Re: [PATCH] [RFC] Closures and lambda functions in PHP
| From: | Christian Seiler | Date: | Wed, 18 Jun 2008 11:44:30 +0000 |
| Subject: | Re: [PATCH] [RFC] Closures and lambda functions in PHP | ||
| References: | 1 2 3 4 | Groups: | php.internals |
| Request: | Send a blank email to internals+get-38346@lists.php.net to get a copy of this message | ||
Hi!
No, that's not what I meant. The engine uses the following internal trick: a) Upon copmilation, my patch simply adds the lambdas as normalIf one knew how to access it, which it seems is not possible/feasible for user-space code.- 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.
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