Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:42702 Return-Path: Mailing-List: contact internals-help@lists.php.net; run by ezmlm Delivered-To: mailing list internals@lists.php.net Received: (qmail 73286 invoked from network); 19 Jan 2009 11:47:34 -0000 Received: from unknown (HELO lists.php.net) (127.0.0.1) by localhost with SMTP; 19 Jan 2009 11:47:34 -0000 Authentication-Results: pb1.pair.com header.from=helly@php.net; sender-id=unknown Authentication-Results: pb1.pair.com smtp.mail=helly@php.net; spf=unknown; sender-id=unknown Received-SPF: unknown (pb1.pair.com: domain php.net does not designate 85.214.94.56 as permitted sender) X-PHP-List-Original-Sender: helly@php.net X-Host-Fingerprint: 85.214.94.56 aixcept.net Linux 2.6 Received: from [85.214.94.56] ([85.214.94.56:38133] helo=h1149922.serverkompetenz.net) by pb1.pair.com (ecelerity 2.1.1.9-wez r(12769M)) with ESMTP id 99/D3-50097-45864794 for ; Mon, 19 Jan 2009 06:47:33 -0500 Received: from MBOERGER-ZRH.corp.google.com (p5086450E.dip.t-dialin.net [80.134.69.14]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (No client certificate requested) by h1149922.serverkompetenz.net (Postfix) with ESMTP id 17CF711F26C; Mon, 19 Jan 2009 12:47:29 +0100 (CET) Date: Mon, 19 Jan 2009 12:47:28 +0100 Reply-To: Marcus Boerger X-Priority: 3 (Normal) Message-ID: <1841779987.20090119124728@marcus-boerger.de> To: Christian Seiler CC: Stanislav Malyshev , Ionut Gabriel Stan , In-Reply-To: <4973B415.4030708@gmx.net> References: <1016989528.20090112233204@marcus-boerger.de> <496BCD33.4080703@zend.com> <133861245.20090113225847@marcus-boerger.de> <496D2074.3070303@zend.com> <496DD269.4050701@gmail.com> <496E12CE.2080403@zend.com> <1956503865.20090117212630@marcus-boerger.de> <4973B415.4030708@gmx.net> MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit Subject: Re: [PHP-DEV] [RFC] prototyping From: helly@php.net (Marcus Boerger) 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