# PATCH: anonymous functions in PHP

- Thread: https://externals.io/message/28399
- Original archive: https://news-web.php.net/php.internals/28399
- Started by Wez Furlong on 2007-03-18T23:41:16+00:00
- Messages: 89
- RFC: Short Closures 2.0 (Declined) — https://externals.io/rfc/auto-capture-closure.md
- Page 1 of 4: messages 1 to 25 (25 per page, add `?page=N` to the URL)
- Next page: https://externals.io/message/28399.md?page=2
- Reply by email to internals@lists.php.net
- More for agents: search with https://externals.io/search.md?q={query} · RFCs at https://externals.io/rfc.md · JSON API at https://externals.io/api · documentation at https://externals.io/llms.txt

---

## #28399 — Wez Furlong — 2007-03-18T23:41:16+00:00

URL: https://externals.io/message/28399#28399

We've been daydreaming about the ability to do something like this in  
PHP:

$data = array("zoo", "orange", "car", "lemon", "apple");
usort($data, function($a, $b) { return strcmp($a, $b); });
var_dump($data); # data is sorted alphabetically

In the past, people have been told to use the travesty that is  
create_function() to emulate this kind of behavior, well, it turns  
out to be a minor patch to the parser to pull that into the core.

You can find my prototype patch at http://pastebin.ca/400871 (against  
PHP_5_2)

The way it works is by making the expression:

function() {}

evaluate to the (generated) name of the anonymous function.

$foo = function() {};

sets $foo to a string like "__zend_anon_1", which can then be passed  
around as a callback name, just like the way that create_function()  
works, except that you don't need to use crazy quoting to declare any  
kind of moderately complex function.

There's one minor flaw in my implementation for ZTS enabled systems  
(just need to move the anon function counter into CG() to solve that.

So, the question is, do we want this in PHP?

--Wez.

---

## #28400 — Jan Lehnardt — 2007-03-18T23:54:00+00:00

URL: https://externals.io/message/28399#28400 · In reply to: #28399

> So, the question is, do we want this in PHP?

yes, please.

Jan

---

## #28401 — Wez Furlong — 2007-03-19T00:30:37+00:00

URL: https://externals.io/message/28399#28401 · In reply to: #28399

I found another flaw; when used in a loop it keeps trying to declare  
the same function over and over.   I think this is because the  
ZEND_DECLARE_FUNCTION opcode is emitted as part of the arg list  
building op sequence.

I'm poking to find an elegant way to fix that.

--Wez.



> You can find my prototype patch at http://pastebin.ca/400871  
> (against PHP_5_2)

> There's one minor flaw in my implementation for ZTS enabled systems  
> (just need to move the anon function counter into CG() to solve that.

---

## #28402 — Jim Wilson — 2007-03-19T00:45:29+00:00

URL: https://externals.io/message/28399#28402 · In reply to: #28401

> > […]
>
> yes, please.

Anonymous function declaration is one of the things I've always loved in
Ruby and JavaScript - I for one would _love to see this_ in php.

-- Jim R. Wilson (jimbojw)


>
> I found another flaw; when used in a loop it keeps trying to declare
> the same function over and over.   I think this is because the
> ZEND_DECLARE_FUNCTION opcode is emitted as part of the arg list
> building op sequence.
>
> I'm poking to find an elegant way to fix that.
>
> --Wez.
>
> 
>
> > […]
>
> > […]

---

## #28403 — Wez Furlong — 2007-03-19T00:48:31+00:00

URL: https://externals.io/message/28399#28403 · In reply to: #28401

Updated patch at http://pastebin.ca/400952
Not 100% sure if my hack in zend_compile.c is righteous, but it  
doesn't seem too far wrong.

--Wez.

---

## #28404 — Gwynne — 2007-03-19T01:27:12+00:00

URL: https://externals.io/message/28399#28404 · In reply to: #28403

> Updated patch at http://pastebin.ca/400952
> Not 100% sure if my hack in zend_compile.c is righteous, but it  
> doesn't seem too far wrong.

74. +                       if (!memcmp(opline- 
 >op2.u.constant.value.str.val, "__zend_anon_", sizeof 
("__zend_anon_")-1)) {

Pardon my nitpicking, but shouldn't this be:

74. +                       if (!memcmp(opline- 
 >op2.u.constant.value.str.val, "__zend_anon_", strlen 
("__zend_anon_")-1)) {

Also, a strong +1 for this patch, I'd love to see this support in PHP.

-- Gwynne, Daughter of the Code
"This whole world is an asylum for the incurable."

---

## #28405 — Wez Furlong — 2007-03-19T01:30:37+00:00

URL: https://externals.io/message/28399#28405 · In reply to: #28404

Your nitpicking happens to be wrong ;-)

sizeof("string constant") is the "same" as strlen("string constant") 
+1, but is resolved at compile time, so we use sizeof("string  
constant")-1 to get a compile time evaluated `strlen()`.  This trick is  
used throughout the PHP internals.

--Wez.



> 
> > […]
>
> 74. +                       if (!memcmp(opline- 
> > […]
> ("__zend_anon_")-1)) {
>
> Pardon my nitpicking, but shouldn't this be:
>
> 74. +                       if (!memcmp(opline- 
> > […]
> ("__zend_anon_")-1)) {
>
> Also, a strong +1 for this patch, I'd love to see this support in PHP.
>
> -- Gwynne, Daughter of the Code
> "This whole world is an asylum for the incurable."

---

## #28406 — Stanislav Malyshev — 2007-03-19T02:06:41+00:00

URL: https://externals.io/message/28399#28406 · In reply to: #28399

> $data = array("zoo", "orange", "car", "lemon", "apple");
> usort($data, function($a, $b) { return strcmp($a, $b); });
> var_dump($data); # data is sorted alphabetically

What happens if you do this:

$data = array("zoo", "orange", "car", "lemon", "apple");
$rev = 1;
usort($data, function($a, $b) { return $rev?strcmp($a, $b):!strcmp($a, 
$b); });
var_dump($data); # data is sorted alphabetically

This works in Javascript (probably Ruby too), but quite hard to make 
work in PHP because $rev is in different scope.
Moreover, would it mean that this:
$f = function($a, $b) { return $rev?strcmp($a, $b):!strcmp($a, $b); }
would work too? Keeping right value of $rev?
-- 
Stanislav Malyshev, Zend Products Engineer
stas@zend.com  http://www.zend.com/

---

## #28407 — Gwynne — 2007-03-19T02:13:28+00:00

URL: https://externals.io/message/28399#28407 · In reply to: #28405

> Your nitpicking happens to be wrong ;-)
>
> sizeof("string constant") is the "same" as strlen("string constant") 
> +1, but is resolved at compile time, so we use sizeof("string  
> constant")-1 to get a compile time evaluated `strlen()`.  This trick  
> is used throughout the PHP internals.

Ah. I've never seen it used that way before; I apologize for my  
ignorance :). In my experience, `sizeof()` on a character constant  
would evaluate as sizeof( const char * const ).


> 
> > […]

-- Gwynne, Daughter of the Code
"This whole world is an asylum for the incurable."

---

## #28408 — Wez Furlong — 2007-03-19T02:18:53+00:00

URL: https://externals.io/message/28399#28408 · In reply to: #28406

I didn't make it do anything fancy with scoping; it would make the  
implementation more complicated, and wouldn't fit so well with the  
way that scoping works in PHP, in that you need to explicitly  
reference the global scope to "break out" of your function scope.

It would be cool if the lexical scope was inherited, but maybe not  
cool enough to warrant making it work :)

--Wez.




> > […]
>
> What happens if you do this:
>
> $data = array("zoo", "orange", "car", "lemon", "apple");
> $rev = 1;
> usort($data, function($a, $b) { return $rev?strcmp($a, $b):!strcmp 
> ($a, $b); });
> var_dump($data); # data is sorted alphabetically
>
> This works in Javascript (probably Ruby too), but quite hard to  
> make work in PHP because $rev is in different scope.
> Moreover, would it mean that this:
> $f = function($a, $b) { return $rev?strcmp($a, $b):!strcmp($a, $b); }
> would work too? Keeping right value of $rev?
> -- 
> Stanislav Malyshev, Zend Products Engineer
> stas@zend.com  http://www.zend.com/

---

## #28409 — Stanislav Malyshev — 2007-03-19T05:43:46+00:00

URL: https://externals.io/message/28399#28409 · In reply to: #28408

> I didn't make it do anything fancy with scoping; it would make the 
> implementation more complicated, and wouldn't fit so well with the way 
> that scoping works in PHP, in that you need to explicitly reference the 
> global scope to "break out" of your function scope.
> 
> It would be cool if the lexical scope was inherited, but maybe not cool 
> enough to warrant making it work :)

Well, making it work makes this thing closure. Otherwise it's just a 
nice way to save a couple of keystrokes :) Not to diminish your work, 
but there's a danger people would think it is closure because it looks 
like one (i.e., in other languages closures look exactly this way, e.g. 
Javascript).
-- 
Stanislav Malyshev, Zend Products Engineer
stas@zend.com  http://www.zend.com/

---

## #28410 — Marcus Boerger — 2007-03-19T07:48:00+00:00

URL: https://externals.io/message/28399#28410 · In reply to: #28407

Hello Gwynne,

Monday, March 19, 2007, 3:13:28 AM, you wrote:

> 
> > […]

> Ah. I've never seen it used that way before; I apologize for my  
> ignorance :). In my experience, `sizeof()` on a character constant  
> would evaluate as sizeof( const char * const ).

Actually it is not a "const char *" here. Instead the language generates
a "const char[]" which works asexpected.

Best regards,
 Marcus

---

## #28411 — Marcus Boerger — 2007-03-19T07:54:00+00:00

URL: https://externals.io/message/28399#28411 · In reply to: #28403

Hello Wez,

  interesting solution. Nice work:-)

Monday, March 19, 2007, 1:48:31 AM, you wrote:

> Updated patch at http://pastebin.ca/400952
> Not 100% sure if my hack in zend_compile.c is righteous, but it  
> doesn't seem too far wrong.

> --Wez.




Best regards,
 Marcus

---

## #28414 — dz@bitxtender.com — 2007-03-19T08:39:10+00:00

URL: https://externals.io/message/28399#28414 · In reply to: #28399

Am 19.03.2007 um 00:41 schrieb Wez Furlong:

> We've been daydreaming about the ability to do something like this  
> in PHP:
>
> $data = array("zoo", "orange", "car", "lemon", "apple");
> usort($data, function($a, $b) { return strcmp($a, $b); });
> var_dump($data); # data is sorted alphabetically
>
> So, the question is, do we want this in PHP?

Oh yes. Please. Please. Please :)


David

---

## #28416 — Antony Dovgal — 2007-03-19T09:17:43+00:00

URL: https://externals.io/message/28399#28416 · In reply to: #28399

> We've been daydreaming about the ability to do something like this in  
> PHP:

I don't have any objections, the only requirement from me is that it should be covered by tests as much as possible.
So I would like to encourage people to write tests to support the patch.
If all the people saying "yes, please" write a test case and send it to the list, the coverage would be pretty good, I guess.

Oh, and of course we can't include it into 5.2, it's only for HEAD (and maybe for 5.3, that's up to Ilia).

-- 
Wbr, 
Antony Dovgal

---

## #28417 — Christian Schneider — 2007-03-19T09:20:31+00:00

URL: https://externals.io/message/28399#28417 · In reply to: #28409

Stanislav Malyshev wrote:
> Well, making it work makes this thing closure. Otherwise it's just a 
> nice way to save a couple of keystrokes :) Not to diminish your work, 
> but there's a danger people would think it is closure because it looks 
> like one (i.e., in other languages closures look exactly this way, e.g. 
> Javascript).

Wez' proposal sounds very intriguing. At the same time I wouldn't try to 
add closures to PHP as while they might be a mighty concept they are 
also complicated and have a high WTF factor IMHO. When I first started 
doing more complex things in Javascript I was bitten more than once by 
closures: Sometimes they did what I expected and sometimes they didn't 
(e.g. when using 'this').

But I agree that it would be great to being able to pass context to the 
generated function, preferably not via a global variable.

One possible solution would be to generate the function as a method of 
the current object and expose this so something like
	usort($data, function($a, $b) { return $this->cmp($a, $b); });
could be used.

- Chris

---

## #28419 — Sebastian Bergmann — 2007-03-19T09:34:04+00:00

URL: https://externals.io/message/28399#28419 · In reply to: #28399

Wez Furlong wrote:
> So, the question is, do we want this in PHP?

 Yes, please. (It might even make my userland implementation of of CLOS-
 style generic functions easier.)

-- 
Sebastian Bergmann                          http://sebastian-bergmann.de/
GnuPG Key: 0xB85B5D69 / 27A7 2B14 09E4 98CD 6277 0E5B 6867 C514 B85B 5D69

---

## #28421 — Jacob Santos — 2007-03-19T13:05:18+00:00

URL: https://externals.io/message/28399#28421 · In reply to: #28403

Hey, thanks, I was researching this exact same thing. You make it appear much 
easier than it is. You've just saved me many months, weeks, and hours trying to 
figure out how to mess with the internals of the Zend Engine. For that, I am 
most grateful.

Also, I would love to see this in PHP, at the very least it would negate the 
argument of those who say PHP doesn't support anonymous functions.

Jacob Santos

> Updated patch at http://pastebin.ca/400952
> Not 100% sure if my hack in zend_compile.c is righteous, but it doesn't 
> seem too far wrong.
> 
> --Wez.

---

## #28426 — Jon Parise — 2007-03-19T15:16:06+00:00

URL: https://externals.io/message/28399#28426 · In reply to: #28403

> Updated patch at http://pastebin.ca/400952

This is interesting.  I'm not sure I'll ever use it (I prefer named
functions), but it's a purely optional and folks seem to want
something like this.

One comment on the patch itself:

    static unsigned int anon_count;

I think you should provide an initial value (0?) for this static
counter.  Also, you should protect against wrap-around (which, while
improbably, is possible).

Alternatively, you could change the anonymous function naming scheme
to something like __zend_anon_FILE_LINE_COLUMN, but that could be an
unnecessary waste of string memory.

-- 
Jon Parise (jon of php.net) :: The PHP Project (http://www.php.net/)

---

## #28428 — Stanislav Malyshev — 2007-03-19T15:44:32+00:00

URL: https://externals.io/message/28399#28428 · In reply to: #28421

> Also, I would love to see this in PHP, at the very least it would negate 
> the argument of those who say PHP doesn't support anonymous functions.

This argument is false in any case, since create_function exists and 
this implementation is other way to write create_function :)

-- 
Stanislav Malyshev, Zend Products Engineer
stas@zend.com  http://www.zend.com/

---

## #28431 — Sean Coates — 2007-03-19T18:30:49+00:00

URL: https://externals.io/message/28399#28431 · In reply to: #28409

> Well, making it work makes this thing closure. Otherwise it's just a
> nice way to save a couple of keystrokes :) Not to diminish your work,
> but there's a danger people would think it is closure because it looks
> like one (i.e., in other languages closures look exactly this way, e.g.
> Javascript).

I think this is the key to this whole discussion.

I doubt PHP will ever be able to fully support JavaScript- and
Lisp-style closures.

To take Stas' example to another level:

function foo()
{
  $bar = function () { echo "bar"; }
  return $bar;
}
$bar = foo();
$bar(); // does this work?

According to our current current syntax:
function foo()
{
  $bar = create_function('', 'echo "bar";');
  return $bar;
}
$bar = foo();
$bar(); // this does work

I don't know Lisp very well at all, but in JavaScript, functions are
general containers, and this sort of thing works fine, albeit VERY
differently. PHP's scoping rules are already completely different from
JavaScript's (there's no way to access the parent scope unless it
happens to be the global scope).

I strongly prefer Wez's syntax for anonymous function declaration. It
would help on many levels, from readability to syntax-highlighting, to
optimization (maybe...).

On optimization, the question becomes "how does Wez's proposal tokenize?"

--------Old:
 `T_OPEN_TAG`                    : <?php
 `T_VARIABLE`                    : $bar
 `T_WHITESPACE`                  :
                                =
 `T_WHITESPACE`                  :
 `T_STRING`                      : create_function
                                (
 `T_CONSTANT_ENCAPSED_STRING`    : ''
                                ,
 `T_WHITESPACE`                  :
 `T_CONSTANT_ENCAPSED_STRING`    : 'echo "bar";'
                                )
                                ;
--------New:
 `T_OPEN_TAG`                    : <?php
 `T_VARIABLE`                    : $bar
 `T_WHITESPACE`                  :
                                =
 `T_WHITESPACE`                  :
 `T_FUNCTION`                    : function
                                (
                                )
 `T_WHITESPACE`                  :
                                {
 `T_WHITESPACE`                  :
 `T_ECHO`                        : echo
 `T_WHITESPACE`                  :
 `T_CONSTANT_ENCAPSED_STRING`    : "bar"
                                ;
 `T_WHITESPACE`                  :
                                }
                                ;

If the answer is "New", then this could be compiled at.. well,
compile-time, not at execute time. That could be even more interesting.

(sorry for the long post.. most of it's code (-; )

S

---

## #28432 — Stanislav Malyshev — 2007-03-19T18:48:05+00:00

URL: https://externals.io/message/28399#28432 · In reply to: #28431

> If the answer is "New", then this could be compiled at.. well,
> compile-time, not at execute time. That could be even more interesting.

If it would create anonymous function compile-time, it would be a big 
advantage to Wez's patch because then this function could be cached. 
Thinking about this, maybe it is the reason enough to do this even if 
it's not real closure.
-- 
Stanislav Malyshev, Zend Products Engineer
stas@zend.com  http://www.zend.com/

---

## #28434 — Sean Coates — 2007-03-19T19:25:10+00:00

URL: https://externals.io/message/28399#28434 · In reply to: #28432

> If it would create anonymous function compile-time, it would be a big
> advantage to Wez's patch because then this function could be cached.
> Thinking about this, maybe it is the reason enough to do this even if
> it's not real closure.

On mulling this over a bit more, other than quick one-off callbacks
(which would definitely benefit from avoiding compile on every request),
one of the key reasons to use create_function() is actually to create
dynamic functions:

$fancyVer = create_function('', 'return "PHP " . `phpversion()`;');
// could be optimized as:
$fancyVer = create_function('', 'return "PHP ' . `phpversion()` .'";');

(the latter only calls `phpversion()` at declaration, not each time the
lambda runs)

Since `phpversion()` is available globally, this isn't a problem. But what
happens if we want to use a variable, instead?

$ver = `phpversion()`;
$fancyVer = create_function('', "return 'PHP $ver';");
// this currently works ^

How would this be rewritten, though?
$ver = `phpversion()`;
$fancyVer = function () { return "PHP $ver"; };

where would $ver come from? the parent scope? the lambda's local scope?
what if it's defined in both places?

S

---

## #28435 — Wez Furlong — 2007-03-19T19:33:27+00:00

URL: https://externals.io/message/28399#28435 · In reply to: #28434

I've been thinking about this on and off today too.
Something along the lines of the following is more in the PHP spirit:

$ver = `phpversion()`;
$fancyVer = function () { lexical $ver; return "PHP $ver"; };

Where "lexical" is a keyword that means "inherit this variable from  
the current lexical scope".  I'm not suggesting that this is a good  
name for the keyword, it's just something that springs to mind.

So, given some way to explicitly reference the scope where the  
function was "defined", what happens when you call $fancyVer after  
that scope has gone away:

function doSomething() {
   $ver = `phpversion()`;
   return function () { lexical $ver; return "PHP $ver"; };
}
$func = doSomething();
$func(); # the doSomething() scope (hash table) doesn't exist any more

This could perhaps be solved by taking a reference to $ver when the  
function is bound, but I don't know enough about the ZE to understand  
the implications of that; it would probably require a bit more state  
tracking per zend_function so that we know that we need to do that  
step during binding.

--Wez.



> > […]
>
> On mulling this over a bit more, other than quick one-off callbacks
> (which would definitely benefit from avoiding compile on every  
> request),
> one of the key reasons to use create_function() is actually to create
> dynamic functions:
>
> $fancyVer = create_function('', 'return "PHP " . `phpversion()`;');
> // could be optimized as:
> $fancyVer = create_function('', 'return "PHP ' . `phpversion()` .'";');
>
> (the latter only calls `phpversion()` at declaration, not each time the
> lambda runs)
>
> Since `phpversion()` is available globally, this isn't a problem. But  
> what
> happens if we want to use a variable, instead?
>
> $ver = `phpversion()`;
> $fancyVer = create_function('', "return 'PHP $ver';");
> // this currently works ^
>
> How would this be rewritten, though?
> $ver = `phpversion()`;
> $fancyVer = function () { return "PHP $ver"; };
>
> where would $ver come from? the parent scope? the lambda's local  
> scope?
> what if it's defined in both places?
>
> S

---

## #28437 — Sean Coates — 2007-03-19T19:45:12+00:00

URL: https://externals.io/message/28399#28437 · In reply to: #28435

If we can solve the scoping problem (perhaps via references as you
mentioned), then lexical (or another keyword, to be debated endlessly
for months, whose name-debate will delay the implementation of this
functionality, but I digress...) seems like a good solution to grabbing
scope, and fits the "PHP Way", IMO.

> So, given some way to explicitly reference the scope where the function
> was "defined", what happens when you call $fancyVer after that scope has
> gone away:

This was my next question (-:

> This could perhaps be solved by taking a reference to $ver when the
> function is bound, but I don't know enough about the ZE to understand
> the implications of that; it would probably require a bit more state
> tracking per zend_function so that we know that we need to do that step
> during binding.

JavaScript (and I suspect other Lisp-like languages) solves this by
making the function an actual closure—the defined function maintains
access to the parent scope, even after the parent's hash table (or
however it works in JS) would have normally been destroyed.

I think the key thing to remember here is that JS is fundamentally
different from PHP. Functions are objects in JS, and they always have
access to variables from all parent scopes. I don't think PHP can (or
should) ever implement this.

I also don't know what I'm talking about when it comes to ZE internals,
so if I'm way off base, feel free to put me in line (-:

(I maintain that JS' wonky (though useful) scoping rules should never be
assimilated in PHP.)

S

---

Next page: https://externals.io/message/28399.md?page=2
