Re: [RFC] prototyping
| From: | Ionut Gabriel Stan | Date: | Wed, 14 Jan 2009 11:54:17 +0000 |
| Subject: | Re: [RFC] prototyping | ||
| References: | 1 2 3 4 | Groups: | php.internals |
| Request: | Send a blank email to internals+get-42616@lists.php.net to get a copy of this message | ||
On 1/14/2009 01:15, Stanislav Malyshev wrote:
Hi!That is one example of convoluted code that is already possible. If a developer creates such a mess is his fault. As Marcus said, it is already possible to call properties. All we have to do is implement __call(). Not being forced to implement __call in cases where we want object augmentation with lambdas is just syntactic sugar. I agree though that a rebind()/setThis() method on the Closure object is needed, but $this should be rebound by default. Auto-magically binding $this to the current object, whatever that may be, is a missing feature that requires a developer to pass the instance as an argument to that lambda. class A {There are a lot of different features in PHP, that's not the reason to turn the engine into a salad of special cases and exceptions. That's why making Closure an object and having __invoke was a good idea - because it fits what the engine already does very well. Having special case just for one class doesn't fit it so well.Also, this adds very new thing to PHP - objects that change while being assigned. I am not sure it is a good thing.Well Closures are a brand new thing in PHP. So far we had nothing even remotely close to the closures we have right now.you wrote. And in my opinion it also makes the most sense. A closure keeps its state.I consider $this be a part of this state. Maybe if you really need it it'd be fine to do something like $closure->setThis($object)...Why would you call __get() here? Becasue I did that by mistake in my veryImagine such code: class Contains { private $_store; function __set($n, $v) { $this->_store[$n] = $v; } function __get($n) { return $this->_store[$n]; } } Pretty standard class. Now imagine you do this: $c = new Contains(); $c->foo = "bar"; echo $c->foo; works well and basically you see Contains as regular object and couldn't care less it has handlers and not properties. Now this: $c->bar = function() { } $c->bar(); what happens here? You can't get the value of "bar" without calling __get and if you call __call instead that means you either lost $c->bar or you have two kinds of properties now - ones that call __get and ones that don't. I don't see how it is good.
protected $_lambdas = array();
public function __set($name, $lambda)
{
$this->_lambdas[$name] = $lambda;
}
public function __call($lambda, $args)
{
$args = array_merge($this, $args);
return call_user_func_array($this->_lambdas[$lambda], $args);
}
}
$a = new A;
$a->foo = function($this) {
var_dump($this);}; $a->foo(); Now, the above code is already possible, but... by implementing callable properties a developer is spared the effort of implementing a __call() method. The only problem now is that by deciding to not implement __call() he loses a nice opportunity of passing the instance object and he's left with some ugly alternatives: class B {} $b = new B; $b->foo = function($this) {
var_dump($this);}; $b->foo($b); // how would be the above call different than? $foo = function($object) {
var_dump($object);} $foo($b); // Or would you believe this is just fine? $b->foo = function() use ($b) {
var_dump($b);}; $b->foo(); Sooner or later, people would want that use statement to be dismissed for an auto-magically bound $this. It's also the problem of accessing protected and private members. JavaScript does automatic binding of the "this" instance which is actually a key point in prototyping. Well, strictly speaking this is not prototyping, is augmentation. Adding members to the JS prototype object means that *any* existing and further instances will share those members. Augmentation is just about adding members to an existing instance. Real prototyping is though possible in PHP 5.3: class A {
/**
* Lambdas available to all instances
*/
public static $lambda;
public function __call($method, $args)
{
$method = self::$lambda;
return call_user_func_array($method, $args);
}
}
A::$lambda = function() {
return 'foo';}; $a1 = new A; $a2 = new A; echo $a1->lambda(); echo $a2->lambda(); The problem of binding $this appears again, but I believe this use case is much harder to solve because there's no $this in the moment we add lambdas to the class. Anyway, it's a point to consider.
In this particular case I think having callable properties has less to do with closures (I mean capturing context, not lambdas) and more to do with extending functionality of existing classes and objects without a need for creating additional classes. It's also about overriding existent behavior. In JavaScript, if you don't have a String.trim() function you add it to the prototype. From that moment on, all your strings have a trim() method: String.prototype.trim = function() {}; Of course, this is nice for primitive types that have some form of syntactic sugar representation, quotes in case of strings. So now I'm able to do: var trimmed = " has spaces ".trim(); This is of course not the case with PHP, but as I said, this feature has less to do with closures.No, becasue Closure cannot be derived as it is a final class. If we changeWhy it's a final class? Any special reason for that?It matters how you bind static variables to it as they are taken from the context. And by the binding you keep the context. Sure all right. But we bind this in a completely different way.I see no reason to have two contexts in closure - one bound one way and another bound another way. I think context is a context - it's where the closure was created.different from an assignment outside the class? And it gets even better, if you assign that closure to another object it gets private access to the other classes members; plus you can bind it to the new classes privateThat's the whole point of the closure - if you make object-bound closure you can pass accessors to private variables to external classes. If you don't want it - either don't access privates in your closure or make the closure static. The whole point of the closure is that you can keep the context with the closure and thus give other scope regulated access to your scope.
I don't see a reason for this. But people might differ. And at the end of the day the problems arising from this are the least evil.The least between what and what? I don't see any problem or "evil" with what we have now except for missing exotic feature of rebinding closures - which would be a huge WTF for me if all closures worked this way (I definitely expect closure to keep its context and not switch it each time I pass it around), but if there was a special way to make it work this way (as setThis mentioned above or any other way) and it would not conflict with other features and not require dirty hacks on the engine I wouldn't mind.