Edit report at http://bugs.php.net/bug.php?id=50980&edit=1

 ID:               50980
 Comment by:       a at b dot c dot de
 Reported by:      nate at frickenate dot com
 Summary:          lambda/anonymous functions do not have proper scope
 Status:           Open
 Type:             Feature/Change Request
 Package:          Feature/Change Request
 Operating System: Linux
 PHP Version:      5.3.1

 New Comment:

use() parameters are early binding - they use the variable's value at
the point where the lambda function is declared, rather than the point
where the lambda function is called (late binding).



If you want late binding then there is already syntax for that ('&') -
which works as expected to propagate assignments up to the calling
scope. To make it the default would be (a) contrary to the way
"ordinary" function parameters are passed, (b) require the addition of
new syntax to indicate that early binding is wanted, (c) deal with the
situation where a used variable doesn't exist at call time, (d) make it
hard to understand the difference between an ordinary function parameter
and a closure.



The problem with allowing variables in the outer scope to leak by
default into the lambda function is that a copy of the ENTIRE collection
returned by get_defined_vars() would need to be persisted for the
lifetime of the closure object (less any that are overridden by
call-time arguments) just in case any of them are needed.



'I imagine this has to do with the php5 "fake references" that are used
to pass objects around.'

The mechanism is the same as that used in Java and C#: the object
reference is passed by value. It's just that in PHP you also have the
option of passing the reference by reference as well (see the chapter in
the manual on "Objects and references", where it explains that what is
passed is an "object identifier" and not a PHP "reference").


Previous Comments:
------------------------------------------------------------------------
[2010-02-09 22:53:17] nate at frickenate dot com

Description:
------------
The way in which variables are passed to the inside of a lambda function
via the 'use (...)' syntax is broken. The entire concept of "scope" or
"closures" does not seem to apply in php whatsoever.



Variables within a lambda are supposed to be resolved/looked up in the
parent scope at runtime when the variable is actually accessed within
the lambda function's code. It seems that php just dumbly creates a copy
of the variable when it sees the definition of the lambda function with
the 'use (...)' clause. Changes to the variable in the parent scope
between the moment where the lambda is defined and the lambda function
is called are not propagated.



The worst part happens with objects. When passing in an object instance
to a lambda, any changes to the original object in the parent scope
after the lambda definition do affect the same object (problem 4 in
reproduce code), but replacing the object altogether does not (problem 3
in reproduce code). I imagine this has to do with the php5 "fake
references" that are used to pass objects around.



I get the feeling this is just a horrible limitation of the devs having
hacked a "best-possible" implementation of "lambdas" into a Zend
codebase that can't handle proper scope lookup. If this is the case, it
would be nice if the documentation could be updated to really nail home
the fact that there is no scoping going on at all, and that quite
literally all you get is a copy of the variable as it exists at the
moment the lambda is defined (except for the strangeness going on with
objects).



It's unfortunate that the "solution" for the time being is to *always*
pass in *every* variable as a reference, which results in expected
output. But this requires creating a local copy within the lambda for
every variable that one wants to modify without affecting the parent
scope. :'(

Reproduce code:
---------------
<?php



// problem 1: this should echo "Canada", not a php notice

$fn = function () use ($country) { echo $country . "\n"; };

$country = 'Canada';

$fn();





// problem 2: this should echo "Canada", not "UnitedStates"

$country = 'UnitedStates';

$fn = function () use ($country) { echo $country . "\n"; };

$country = 'Canada';

$fn();





// problem 3: this should echo "Canada", not "UnitedStates"

$country = (object)array('name' => 'UnitedStates');

$fn = function () use ($country) { echo $country->name . "\n"; };

$country = (object)array('name' => 'Canada');

$fn();





// problem 4: this outputs "Canada". if this outputs "Canada",

// then so should problem 2 above. otherwise this should be

// just as broken as problem 2 and be outputting "UnitedStates"

$country = (object)array('name' => 'UnitedStates');

$fn = function () use ($country) { echo $country->name . "\n"; };

$country->name = 'Canada';

$fn();



?>

Expected result:
----------------
If scope was actually handled properly, then the lookup of the "use
(...)"'d variable would occur at the moment the variable is used within
the lambda, resulting in the following output:



Canada

Canada

Canada

Canada

Actual result:
--------------
PHP Notice:  Undefined variable: country in ... on line 5



UnitedStates

UnitedStates

Canada


------------------------------------------------------------------------



-- 
Edit this bug report at http://bugs.php.net/bug.php?id=50980&edit=1

Reply via email to