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

--
PHP Internals - PHP Runtime Development Mailing List
To unsubscribe, visit: http://www.php.net/unsub.php

Reply via email to