The original anonymous functions patch[1] contained support for
FUNCTION as a recursion mechanism in closures, such that I should
be able to do something like this:
$factorial = function($n) {
if ($n == 1)
return 1;
else
return $n * call_user_func(FUNCTION, $n - 1);
};
print $factorial(3); // => 6
It fails with a warning, though:
Warning: call_user_func()
expects parameter 1 to be a valid
callback, function '{closure}' not found or invalid function name
Is there another recursion mechanism for closures besides something
like the $GLOBALS['factorial'] hack?
Footnotes:
[1] http://marc.info/?l=php-internals&m=119995982228453&w=2
Hi Peter,
If I recall correctly, you can use the 'use' keyword.
$factorial = function($foo) use ($factorial) {
$factorial($foo);
}
$factorial('Hello World!');
I'm still having issues compiling 5.3 on my system, so I haven't tested
this.
Thanks,
Justin Martin
Peter Danenberg wrote:
The original anonymous functions patch[1] contained support for
FUNCTION as a recursion mechanism in closures, such that I should
be able to do something like this:$factorial = function($n) {
if ($n == 1)
return 1;
else
return $n * call_user_func(FUNCTION, $n - 1);
};print $factorial(3); // => 6
It fails with a warning, though:
Warning:
call_user_func()
expects parameter 1 to be a valid
callback, function '{closure}' not found or invalid function nameIs there another recursion mechanism for closures besides something
like the $GLOBALS['factorial'] hack?Footnotes:
[1] http://marc.info/?l=php-internals&m=119995982228453&w=2
In addendum I'd like to correct the syntax that I had someone in IRC test.
Apparently it works as such:
$foo = NULL;
$foo = function($foo) use (&$foo) {
...
}
Still rather hackish, but better than globals I suppose?
Thanks,
Justin Martin
Justin Martin wrote:
Hi Peter,
If I recall correctly, you can use the 'use' keyword.
$factorial = function($foo) use ($factorial) {
$factorial($foo);
}$factorial('Hello World!');
I'm still having issues compiling 5.3 on my system, so I haven't tested
this.Thanks,
Justin MartinPeter Danenberg wrote:
The original anonymous functions patch[1] contained support for
FUNCTION as a recursion mechanism in closures, such that I should
be able to do something like this:$factorial = function($n) {
if ($n == 1)
return 1;
else
return $n * call_user_func(FUNCTION, $n - 1);
};print $factorial(3); // => 6
It fails with a warning, though:
Warning:
call_user_func()
expects parameter 1 to be a valid
callback, function '{closure}' not found or invalid function nameIs there another recursion mechanism for closures besides something
like the $GLOBALS['factorial'] hack?Footnotes:
[1] http://marc.info/?l=php-internals&m=119995982228453&w=2
Quoth Justin Martin on Pungenday, the 30th of Discord:
Apparently it works as such:
$foo = NULL;
$foo = function($foo) use (&$foo) {
...
}Still rather hackish, but better than globals I suppose?
Heh; amazing. I'm not going to pretend to comprehend this hack; but
I'll use it, nonetheless.
Well, it's a rather simple bit of logic.
First, we define $foo and load it with NULL
so that it is available for
referencing. Next, in terms of program logic, we create a closure with a
lexical ('use') variable of a reference to $foo, which is then assigned
to $foo. Thus, the reference to $foo in the closure declaration now
points to the closure itself.
Recursion is always complicated :P
Thanks,
Justin Martin
Peter Danenberg wrote:
Quoth Justin Martin on Pungenday, the 30th of Discord:
Apparently it works as such:
$foo = NULL;
$foo = function($foo) use (&$foo) {
...
}Still rather hackish, but better than globals I suppose?
Heh; amazing. I'm not going to pretend to comprehend this hack; but
I'll use it, nonetheless.
First, we define $foo and load it with
NULL
so that it is available for
referencing.
It turns out loading $foo is superfluous; I can get away with just:
$foo = function($foo) use (&$foo) {
$foo();
}
Next, in terms of program logic, we create a closure with a lexical
('use') variable of a reference to $foo, which is then assigned to
$foo. Thus, the reference to $foo in the closure declaration now
points to the closure itself.
That much is clear; but why $foo is all of the sudden bound within the
closure when I use by reference (&$foo) as opposed to use by value
($foo) is mysterious.
I suppose it's an issue of cloning. Perhaps there's some difference
between a cloned closure and a referenced closure?
Thanks,
Justin Martin
Peter Danenberg wrote:
First, we define $foo and load it with
NULL
so that it is available for
referencing.It turns out loading $foo is superfluous; I can get away with just:
$foo = function($foo) use (&$foo) {
$foo();
}Next, in terms of program logic, we create a closure with a lexical
('use') variable of a reference to $foo, which is then assigned to
$foo. Thus, the reference to $foo in the closure declaration now
points to the closure itself.That much is clear; but why $foo is all of the sudden bound within the
closure when I use by reference (&$foo) as opposed to use by value
($foo) is mysterious.
Quoth Justin Martin on Pungenday, the 30th of Discord:
If I recall correctly, you can use the 'use' keyword.
Thanks, Justin; that occurred to me, too. But the following results in
"Notice: Undefined variable: factorial":
$factorial = function($n) use ($factorial) {
if ($n == 1)
return 1;
else
return $n * $factorial($n - 1);
};
print $factorial(3);
and eventually "PHP Fatal error: Function name must be a string."
Peter Danenberg pisze:
Quoth Justin Martin on Pungenday, the 30th of Discord:
If I recall correctly, you can use the 'use' keyword.
Thanks, Justin; that occurred to me, too. But the following results in
"Notice: Undefined variable: factorial":$factorial = function($n) use ($factorial) {
if ($n == 1)
return 1;
else
return $n * $factorial($n - 1);
};print $factorial(3);
and eventually "PHP Fatal error: Function name must be a string."
This is correct.
See: in the first example you were referencing previously defined $foo
(which at the moment of first reference was NULL). It was only later
replaces with closure itself but it worked because of the reference.
On the other hand when you copy variable you try to use undefined
$factorial (thus the notice). It is because at the moment of "using" the
variable it's undefined. Only later it becomes our closure, however then
it's after evaluation of use.
Although this is probably more a generals topic. The lack of
FUNCTION inside the closure is however something i'd like to hear
more about.
Any word on this from the devs?
TIA,
Marcin
Hi!
The original anonymous functions patch[1] contained support for
FUNCTION as a recursion mechanism in closures, such that I should
be able to do something like this:$factorial = function($n) {
if ($n == 1)
return 1;
else
return $n * call_user_func(FUNCTION, $n - 1);
};print $factorial(3); // => 6
You can also do it the hard way (that's how some languages not having
luxury of being PHP do it ;), using something called Y-Combinator
(http://en.wikipedia.org/wiki/Y_combinator), which allows you to unroll
recursive function into non-recursive one. This looks like this:
function Y($F) {
$func = function ($f) { return $f($f); };
return $func(function ($f) use($F) {
return $F(function ($x) use($f) {
$ff = $f($f);
return $ff($x);
});
});
}
This is the generic combinator function. Now your factorial function
would look like:
$factorial = Y(function($fact) {
return function($n) use($fact) {
return ($n == 0)?1:$n*$fact($n-1);
};
});
And then you can do just this:
var_dump($factorial(3));
Look, ma, no recursion! :)
If it looks confusing, then you're right - it is :) It may be not what
you would want to do in your code (or maybe you do), but this is yet
another thing now possible with PHP.
On the other hand, we may want to add some way to refer to the closure
currently being run. We should have this information, so it should not
be too hard to do.
Stanislav Malyshev, Zend Software Architect
stas@zend.com http://www.zend.com/
(408)253-8829 MSN: stas@zend.com
function Y($F) {
$func = function ($f) { return $f($f); };
return $func(function ($f) use($F) {
return $F(function ($x) use($f) {
$ff = $f($f);
return $ff($x);
});
});
}
That's interesting; I should be able to implement tail-recursion in a
similar fashion using, say, trampolines,[1] shouldn't I?
On the other hand, we may want to add some way to refer to the
closure currently being run. We should have this information, so it
should not be too hard to do.
That would be great; we could even do some benchmarking against the
Y-combinator (which I've always loved, but never used).
Footnotes:
[1] http://en.wikipedia.org/wiki/Tail_recursion#Implementation_methods