Hello Christian, Sunday, January 18, 2009, 11:58:29 PM, you wrote:
> Hi Marcus, >>> "Convoluted"? "Mess"? Are you kidding me? It's standard usage of access >>> handlers. >> >> It is a mess right now. You assign a closure to another method and get >> access to the original owners private members. That is not only unexpected >> and contradicting anything that any oyther language ever but but also >> violates our very own basic ideas. That is what I call a mess. > You could also call Javascript's behaviour confusing. A closure is per And I could say that what the two of you designed ofr PHP is not a design but a very confusing incoherent implementation that is based on the lack of being able to get support for something else in the underlying c implementation. You took somethign very special and made it behave somewhat different making it even more specific and different from anyone's expectations, excluding people that understand the underlying c level issues. Either way, we should imo, simply disable $this for clusresm get and get rid of any dynamic class binding mess or make them work. Doing a lot of unexpected behavior is in my opinion very very bad. > definition a function that encloses its scope at creation time. E.g. if > you have the following (in Javascript, PHP, doesn't matter): > var foo; > var closure = function () { > alert (foo); > }; > The current scope variable foo is inherited. The question is: Why > shouldn't the same also happen for the variable this? In the closures > implementatios Dmitry and I designed for PHP, it does. Admittedly, $this > is a special variable because it's implicitly available in normal > methods and thus we decided that for closures you don't need to do "use > ($this)" either. > So the question is: Why does Javascript change the pointer to the this > variable upon calling a method? The answer is simple: Because there is > NO OTHER WAY to define object methods in Javascript. You *always* have > to use object.method = function () { }; or Something.prototype.method = > ... in order to define a callable method. There is no other way. Because > of that, Javascript defines the behaviour with $this. > PHP, on the other hand, since it already does have a method for creating > normal class methods (simply define them in the class { } block), does > not need such a mechanism for normal OOP. > Also, implementing this in PHP may give quite some headaches. Take for > example the following code: > interface Some_Filter { > public function accept ($value); > } > class Closure_Filter implements Some_Filter { > private $closure; > public function __construct (Closure $closure) { > $this->closure = $closure; > } > public function accept ($value) { > // Or something similar, see below > return call_user_func ($this->closure, $value); > } > } > class Foo { > private $min, $max; > public function bar () { > $filter = new Closure_Filter (function ($value) { > return $value >= $this->min && $value <= $this->max; > }); > $data = $something->doSomethingElse ($filter, $data); > } > } > Now, basically, the idea behind this code should be clear: We want to > define a filter, there's an interface for that filter that any class may > define and there's a simple wrapper class for Closures for filters that > are supposed to be extremely simple. I don't think this example is > convoluted, one could easily imagine a similar design in the real word > (probably a bit more complex, but nevertheless). > The filter closure is now defined in the class Foo. Thus one would > assume that the closure is bound to the $this of the Foo object (once > created). But since that closure is passed to the constructor of the > Closure_Filter class, the $this would be rebound according to your > proposal. Thus, when invoking the method, the closure would now try to > access the ->min and ->max properties of Closure_Filter class - which is > clearly not the intention. > Of course, there are possibilites to circumvent that: 1) Copy the min > and max properties to local scope and bind them with use() to the > closure. Or 2) Don't store the closure directly inside a property of the > Closure_Filter class but in an array so $this doesn't get rebound. But > clearly, in my eyes, that is some kind of hackish workaround that really > sucks. > Also, with the implementation Dmitry and I wrote, it is very clear what > the semantics are $this: It is always bound at creation time and that's > it. Just to make a comparison: > // Variant 1: > return call_user_func ($this->closure, $value); > // Variant 2: > $closure = $this->closure; > return $closure ($value); > // Variant 3: > return $this->closure->__invoke ($value); > // Variant 4: > return $this->closure ($value); > Now, the original implementation: > Variant 1: $this bound to Foo class > Variant 2: $this bound to Foo class > Variant 3: $this bound to Foo class > Variant 4: doesn't work, because methods and properties have a > different namespace > Now, an implementation where *all* four variants bind to the > Closure_Filter class: > Variant 1: $this bound to Closure_Filter class > -> Inconsistent: call_user_func ($this->normal_method) will first > cause "undefined property" and then "invalid > callback" errors > Variant 2: $this bound to Closure_Filter class > -> Hmm, so this basically allows for the following code: > $closure = function ...; > $object->closure = $closure; // MAGIC happens! > $closure = $object->closure; // $closure changed - WTF?! > Variant 3: $this bound to Closure_Filter class > -> Ok, this really doesn't matter either way, using __invoke > directly looks a bit weird anyway. > Variant 4: $this bound to Closure_Filter class > -> Calling properties directly will certainly cause resolution > order headaches. Since that was the MAIN point on the list that > was discussed before my posting you will have to admit it at > least is not obvious what the resolution order should be. > Ok, you could say *ONLY* variant 4 should be added with dynamic and > temporary (i.e. call-time and for the duration of the call) rebinding of > the $this context. But that would also cause confusion: Why should $this > for the *same* property on some occasions a first object and on some > another? It doesn't make any sense! > Now, I would call this certainly a bigger mess than the inconsistency > wrt. Javascript. Since PHP is already different from Javascript (access > modifiers, "normal" object methods, different namespaces for methods and > properties), I don't really see the point in forcing something which > will create problems with the concepts PHP already has. > ----------------------- snip ------------------ > However, having said that, I may have a solution which will make > everybody (more or less at least) happy: > Since the main problem of re-binding closures to different objects is > the obscure magic just by assignment, why not make that magic "public"? > I.e.: Add a method bindTo() to the Closure class which returns an > identical copy of the closure (same bound variables etc.) with the > EXCEPTION that the new variables are already bound. This allows for > prototyping in the way that you want it BUT ensures that no silent magic > occurs that creates problems. > Example: > $func = function ($a, $b) { > return $a + $b + $this->bias; > }; > $object->add = $func->bindTo ($object); > Additionally, a static method bind() could be added for direct assignments: > $object->add = Closure::bind ($object, function ($a, $b) { > return $a + $b + $this->bias; > }); > The bindTo/bind functions could also check the current class scope and > ensure that the class scope of the newly bound closure has the same > access level to the object as the current class - that would enforce > access modifiers. Whether or not we want that is probably a matter of > debate. > Another idea which *could* be discussed (I'm not sure about it myself) > is that if you add variant 4 from above (i.e. calling object properties > like methods directly) - which you probably want to do if you want to > allow $this rebinding - and we have finally figured out some sane > resolution rules, one could also add a E_WARNING message when the $this > pointer of the closure does not match the $this pointer of the object > for which the closure is called as a method. So, for example: > $closure = function () { return $this->value; }; > $object->get1 = $closure; > $object->get2 = $closure->bindTo ($object); > call_user_func ($object->get1); > call_user_func ($object->get2); > $f = $object->get1; $f (); > $f = $object->get2; $f (); > $object->get1->__invoke (); > $object->get2->__invoke (); > // -> No warnings > // BECAUSE (!) this syntax makes it clear to the user that > // he is calling *some* closure, but not necessarily a closure > // bound to the specific object > $object->get1 (); > // -> Warning: Closure called directly but object scope does > // not match object closure is being called for! > // -> HOWEVER, execute the closure and DON'T rebind it > $object->get2 (); > // -> No warning (match) > The check should of course be intelligent enough to detect the > following correctly: > $this->foo = function () { ... }; > $this->foo (); > // -> No warning (match, since assigned from inside the own object) > ------------------ snip again -------------------- > So, to sum up my posting: > 1. Stance on direct method calling $foo->closure_property () > - As long as you figure out some sane and consistent resolution > rules: Fine with me. > 2. Stance on *automatic* re-binding of $this > - Really big convoluted mess > - PHP already does things differently from Javascript, has > different concepts. > => I'm against it. > 3. Alternative proposal for making re-binding possible but making > sure this is done explicitly: > - Figure out some sane resolution rules > - Closure::bind and Closure->bindTo > 4. Additional proposal to 3: > - Enforce class scope and thus access modifiers when using > Closure::bind / Closure->bindTo > 5. Additional propsoal to 3: > - Warning when using direct-calling and the closure is not > bound to the object for which the closure is called. > Regards, > Christian Best regards, Marcus -- PHP Internals - PHP Runtime Development Mailing List To unsubscribe, visit: http://www.php.net/unsub.php