Newsgroups: php.internals Path: news.php.net Xref: news.php.net php.internals:38346 Return-Path: Mailing-List: contact internals-help@lists.php.net; run by ezmlm Delivered-To: mailing list internals@lists.php.net Received: (qmail 82721 invoked from network); 18 Jun 2008 11:45:24 -0000 Received: from unknown (HELO lists.php.net) (127.0.0.1) by localhost with SMTP; 18 Jun 2008 11:45:24 -0000 Authentication-Results: pb1.pair.com smtp.mail=chris_se@gmx.net; spf=pass; sender-id=pass Authentication-Results: pb1.pair.com header.from=chris_se@gmx.net; sender-id=pass Received-SPF: pass (pb1.pair.com: domain gmx.net designates 213.165.64.20 as permitted sender) X-PHP-List-Original-Sender: chris_se@gmx.net X-Host-Fingerprint: 213.165.64.20 mail.gmx.net Linux 2.5 (sometimes 2.4) (4) Received: from [213.165.64.20] ([213.165.64.20:35028] helo=mail.gmx.net) by pb1.pair.com (ecelerity 2.1.1.9-wez r(12769M)) with ESMTP id 04/9C-03518-355F8584 for ; Wed, 18 Jun 2008 07:45:24 -0400 Received: (qmail invoked by alias); 18 Jun 2008 11:45:19 -0000 Received: from p54A174D0.dip.t-dialin.net (EHLO chris-se.dyndns.org) [84.161.116.208] by mail.gmx.net (mp051) with SMTP; 18 Jun 2008 13:45:19 +0200 X-Authenticated: #186999 X-Provags-ID: V01U2FsdGVkX1+ioYcrupyiabuFbOct/zxykY8jr0kR/CWxkC3Fpk slUY6O5TWaZglz Received: from [192.168.0.175] (HSI-KBW-091-089-005-213.hsi2.kabelbw.de [91.89.5.213]) by chris-se.dyndns.org (Postfix) with ESMTP id 8864610569; Wed, 18 Jun 2008 13:34:45 +0200 (CEST) Message-ID: <4858F51E.1040701@gmx.net> Date: Wed, 18 Jun 2008 13:44:30 +0200 User-Agent: Thunderbird 2.0.0.14 (X11/20080421) MIME-Version: 1.0 To: Larry Garfield CC: php-dev List References: <4856A547.3080801@gmx.net> <200806162257.24141.larry@garfieldtech.com> <48576A39.9040208@gmx.net> <200806172059.25933.larry@garfieldtech.com> In-Reply-To: <200806172059.25933.larry@garfieldtech.com> X-Enigmail-Version: 0.95.6 Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: 7bit X-Y-GMX-Trusted: 0 Subject: Re: [PHP-DEV] [PATCH] [RFC] Closures and lambda functions in PHP From: chris_se@gmx.net (Christian Seiler) Hi! >>> - I am a little confused about the OOP interaction. How does a function >>> become a public method of the class? >> To clarify: the "public method" ist just the internal representation of >> the lambda function and has *nothing* to do with the semantics of >> calling the lambda itself. The "method" only means that the lambda >> function defined inside another method can access the class members and >> "public" only means that the lambda function can still be called from >> outside the class. > > If one knew how to access it, which it seems is not possible/feasible for > user-space code. No, that's not what I meant. The engine uses the following internal trick: a) Upon copmilation, my patch simply adds the lambdas as normal functions to the function table with an automatically generated unique (!) name. If it happens to be defined within a class method, the function will be added as a public final method to that class. b) That added function is not directly callable due to checks of a flag in the internal structure of that function. c) At the place of the function definition the compiler leaves an opcode "grab function $generatedname and make a closure out of it". This opcode then looks up the generated lambda function, copies the function structure, saves the bound variables in that structure and returns the copied structure as a resource. d) Normally, when a function is called, the name is looked up in the function table. The function structure that is retrieved from there is then used to execute the function. Since a lambda resource is already a function structure, there is no necessity to look up anything in the function table but the function structure can be directly passed on to the executor. Please note step d): The closure functionality only changes the *lookup* of the function - so instead of getting the function structure from a hash table lookup I get the function structure by retrieving it from the resource. But *after* the lookup of a class method there are checks for the access protection of that method. So these access protection checks also apply to closures that were called. If a lambda function was not declared public, it could not be used outside of the class it was defined in. Perhaps this makes it clearer? > I see. It would be great if you could update the RFC with this information so > that it's clearer. Done: http://wiki.php.net/rfc/closures > Two other questions that just occurred to me: > > 1) What is the interaction with namespaces, if any? Are lambdas as > implemented here ignorant of namespace, or do they take the namespace where > they are lexically defined? My patch itself is namespace-ignorant, but the net result is not: a) The generated internal function names do not contain the current namespace name, but since namespace names in function names are only used for lookup if you want to call the function. And calling lambdas by name (!) directly doesn't work anyway (is not supposed to work) so this poses no problem. b) The code *inside* the closure is namespace-aware because the information of which namespace is used is added at compile time. Either the name lookup is done entirely at compile time or the current compiler namespace is automatically added to all runtime lookup calls (this is already the case with current code). So the information which namespace a function resides in is currently *irrelevant* at runtime when calling other functions. For (b) let me make two examples: Suppose you have the following code: namespace Foo; function baz () { return "Hello World!\n"; } function bar () { return function () { echo baz (); }; } and in another file: $lambda = Foo::bar (); $lambda (); This will - as expected - print "Hello World!\n". The reason is that the compiler upon arriving at the baz() function call inside the closure already looks up the function in the function table directly (it knows the current namespace) - and simply creates a series of opcodes that will call the function with the name "Foo::baz" (the lookup is already done at compile time). Consider this other code: foo-bar.php: namespace Foo; function bar () { return function () { echo baz (); }; } foo-baz.php: namespace Foo; function baz () { return "Hello World!\n"; } baz.php: function baz () { return "PHP!\n"; } test1.php: require 'foo-bar.php'; require 'foo-baz.php'; $lambda = Foo::bar (); $lambda (); test2.php: require 'foo-bar.php'; require 'baz.php'; $lambda = Foo::bar (); $lambda (); Running test1.php yields "Hello World!" whereas running test2.php yields "PHP!". Why is this? Because when the compiler reaches the baz () function call in the closure, it cannot find the function so it cannot determine whether it's a function in global or in namespace scope. So it will simply add a series of opcodes that say "try Foo::bar and if that does not exist try bar". Here again, Foo:: is added by the compiler to the opcode because the compiler has the necessary information which namespace the call is currently in. The runtime execution engine NEVER looks at the function as to which namespace the function belongs to, it ONLY looks at the function calling opcodes that the compiler already correctly (!) generates. Please note that I have only described what the PHP engine does with namespaces until now ANYWAY, the closures DO NOT CHANGE ANYTHING related to this. That's why my patch doesn't have to care about namespaces at all but the net-result closures will. > 2) What happens with the following code? > > class Foo { > private $a; > } > > $f = new Foo(); > > $b = 5; > > $f->myfunc = function($c) { > lexical $b; > print $a; // This generates an error, no? > print $b; // This prints 5, right? > print $c; // Should print whatever $c is. > } > > $f->myfunc(3); > > Or is the above a parse error entirely? Well, first of all, it's a parse error because you forgot the ; after the } of the closure. ;-) Then: Closures only have access to the scope in which they are defined in. Whether you assign that closure to a class property or not does not change the semantics of the closure itself. The closure definition is simply the following part: function ($c) { ... }. You then use the part to store the closure as a class property. The closure you define is entirely oblivious of the class since it is not defined inside a class method. Consider this code: function foo () { echo $a; } $f->myfunc = 'foo'; Well, of course, first of all $a will not work anyway because $a has never been the object member access method of PHP. But even if you use $this->a, that won't work with foo either. For the exact same reasons it will not work with closures. Your comments inside the closure are correct though: $a gives an error (notice: undefined variable), $b gives 5 (unless $b is modified later on) and $c gives the current value of the parameter $c. But: As I already said last time, $f->myfunc(3) won't work. That will give "no such method". For the same reason as $f->myfunc = 'foo'; $f->myfunc(); will not work - because -> can only be used to call METHODS, not dynamic functions stored in properties. Regards, Christian