Hi everyone
A few years ago, an RFC was proposed to introduce special syntax for
self-referencing closures, mainly used for implementing recursion.
https://wiki.php.net/rfc/closure_self_reference
The proposed solution allows specifying a variable that will be
assigned the closure object on function entry.
$fibonacci = function (int $n) as $fn {
if ($n === 0) return 0;
if ($n === 1) return 1;
return $fn($n-1) + $fn($n-2);
};
This is essentially already possible through a by-reference capture.
$fibonacci = function (int $n) use (&$fibonacci) { ... };
This cannot be a by-value capture because by the time the closure is
created and variables are bound, $fibonacci is not assigned yet. The
downside of by-reference capturing is that if $fibonacci is
accidentally reassigned, the reference within the closure is also
changed.
However, I consider a language change largely unnecessary. Instead,
this may be solved with a very simple static function:
Closure::getCurrent().
$fibonacci = function (int $n) {
if ($n === 0) return 0;
if ($n === 1) return 1;
$self = Closure::getCurrent();
return $self($n-1) + $self($n-2);
// Or
return Closure::getCurrent()($n-1) + Closure::getCurrent()($n-2);
};
Another suggestion was to introduce a magic constant CLOSURE.
However, this would be misleading, given it can refer to different
closure instances, thus not actually being constant.
You can find the implementation here:
https://github.com/php/php-src/pull/18167
I picked the name Closure::getCurrent() to be consistent with
Fiber::getCurrent(). Contrary to fibers, calling Closure::getCurrent()
from non-closures is nonsensical and thus disallowed, resulting in an
Error exception. It will only succeed when called directly from
closures. It will not recurse the call stack.
Do you have thoughts or concerns with this solution? If not, I'm
planning to merge it into master in a few weeks.
Ilija
Hi
I looked at it earlier, when you opened the PR.
I also remember the discussion from the past.
Your PR is super logical, and a super easy implementation.
Language change seems unnecessary indeed.
LGTM, ship it!
Kind regards
Niels
Hi everyone
A few years ago, an RFC was proposed to introduce special syntax for
self-referencing closures, mainly used for implementing recursion.https://wiki.php.net/rfc/closure_self_reference
The proposed solution allows specifying a variable that will be
assigned the closure object on function entry.$fibonacci = function (int $n) as $fn {
if ($n === 0) return 0;
if ($n === 1) return 1;
return $fn($n-1) + $fn($n-2);
};This is essentially already possible through a by-reference capture.
$fibonacci = function (int $n) use (&$fibonacci) { ... };
This cannot be a by-value capture because by the time the closure is
created and variables are bound, $fibonacci is not assigned yet. The
downside of by-reference capturing is that if $fibonacci is
accidentally reassigned, the reference within the closure is also
changed.However, I consider a language change largely unnecessary. Instead,
this may be solved with a very simple static function:
Closure::getCurrent().$fibonacci = function (int $n) {
if ($n === 0) return 0;
if ($n === 1) return 1;$self = Closure::getCurrent(); return $self($n-1) + $self($n-2); // Or return Closure::getCurrent()($n-1) + Closure::getCurrent()($n-2);
};
Another suggestion was to introduce a magic constant CLOSURE.
However, this would be misleading, given it can refer to different
closure instances, thus not actually being constant.You can find the implementation here:
https://github.com/php/php-src/pull/18167I picked the name Closure::getCurrent() to be consistent with
Fiber::getCurrent(). Contrary to fibers, calling Closure::getCurrent()
from non-closures is nonsensical and thus disallowed, resulting in an
Error exception. It will only succeed when called directly from
closures. It will not recurse the call stack.Do you have thoughts or concerns with this solution? If not, I'm
planning to merge it into master in a few weeks.Ilija
PHP now officially better than JavaScript, which had arguments.callee
since inception, but was then deprecated without replacement.
Hello Ilija,
Nice addition! I thought about edge cases and what the desired output is
with nested closures?
Example:
<?
$a = function (int $numberA) {
$b = function (int $numberB) {
if($numberB < 10) {
return Closure::current(10);
}
return $numberB;
};
return $b($numberA) + $numberA;
};
var_dump($a(4));
I thought about edge cases and what the desired output is with nested closures?
Example:
<?
$a = function (int $numberA) {
$b = function (int $numberB) {
if($numberB < 10) {
return Closure::current(10);
}
return $numberB;
};
return $b($numberA) + $numberA;
};var_dump($a(4));
I'd expect it to refer to current/local/nearest function.
If you wanted a way to call $a from inside $b, I'd expect callable syntax to be able to capture these in-place.
Eg:
$a = function ($a) {
$aFn = Closure::current(...);
$aFn(4) === Closure::current(4);
};
https://www.php.net/manual/en/functions.first_class_callable_syntax.php
This could be use'd to a nested $b function, or otherwise passed/returned to other functions without changing meaning.
That is, I would not expect it to behave like late-static binding where it'll always be for the "current" fn that's invoking it, but rather capture where the callable expression happened.
--
Timo Tijhof,
Wikimedia Foundation.
https://timotijhof.net/
Eg:
$a = function ($a) {
$aFn = Closure::current(...);
$aFn(4) === Closure::current(4);
};
I think there's some confusion on this thread; as I understand it, the proposal is not for a magic function to call the current closure, but a way to get a reference to it.
So you don't need, or want, the FCC syntax here - that would create a closure which, whenever invoked, executes the static method "current" on class "Closure".
What you want is to call the Closure::current (or getCurrent) method directly, which will give you the existing closure, known as $a in the outer scope. Once you have a reference to it, you can pass it around and execute it wherever you want.
$aOutside = function (int $foo) use (&$aOutside) {
// Points to the same object as if you used by-ref
assert($aOutside === Closure::getCurrent());
// Store it wherever you like
$aInside = Closure::getCurrent();
assert($aInside === $aOutside);
// Call it like you would any callable
$aOutside(42);
$aInside(42);
Closure::getCurrent()(42); // note the two sets of parens
// Capture it into another closure
$bOutside = function() use ($aInside) {
// Get a self-reference in that one too
$bInside = Closure::getCurrent();
// Do whatever you like with $aInside and $bInside
};
};
Rowan Tommins
[IMSoP]
I gave this example only to point out that this case should be mentioned in
the RFC to dispel doubts what's intended and eventually put such test case
into the PR.
Kind regards,
Jorg
I gave this example only to point out that this case should be
mentioned in the RFC to dispel doubts what's intended and eventually
put such test case into the PR.
Sure, but there was a mistake in your example - Closure::current(10)
should be Closure::current()(10), i.e. "get the current closure, and
then immediately invoke it, passing 10".
More readable if you put it in a local variable:
$a = function (int $numberA) {
$b = function (int $numberB) {
$selfReference = Closure::getCurrent();
if($numberB < 10) {
return $selfReference(10);
}
return $numberB;
};
return $b($numberA) + $numberA;
};
var_dump($a(4));
The local var $selfReference refers to the same Closure object as the
outer var $b, because that's the closure that's "current" on the line
where Closure::getCurrent() is called.
--
Rowan Tommins
[IMSoP]
Hi everyone
This is essentially already possible through a by-reference capture.
$fibonacci = function (int $n) use (&$fibonacci) { ... };
This cannot be a by-value capture because by the time the closure is
created and variables are bound, $fibonacci is not assigned yet. The
downside of by-reference capturing is that if $fibonacci is
accidentally reassigned, the reference within the closure is also
changed.However, I consider a language change largely unnecessary. Instead,
this may be solved with a very simple static function:
Closure::getCurrent().
How about mutual recursion, where two of these functions call each
other? Currently one can write
$left = function(array $a) use(&$second) { ... };
$second = function(array $a) use($first) { ... };
(The second can capture the first by value, but the first still needs to
capture the second by reference.)
With Closure::getCurrent(), how does either function reference the other?
@Jorg the desired output is 14.
@Morgan $second=function(array $a, callable $first){...}
@Morgan $second=function(array $a, callable $first){...}
This is just as effective as it would be for any use()d variable, so why
do we have use() again?
[Note: in my original mail "$left" should of course have been "$first",
otherwise there wouldn't be any mutual recursion. Sorry about the typo.]
Hi
Am 2025-04-01 12:32, schrieb Morgan:
This is just as effective as it would be for any use()d variable, so
why do we have use() again?
If you are at the point where you need mutually recursive closures,
perhaps you should just use named functions / an anonymous class. I
don't find it useful to “optimize DX” for that case, since
self-recursive Closures are already somewhat questionable / rarely used
(though I think I've used that myself a handful of times in the past).
Best regards
Tim Düsterhus
If you are at the point where you need mutually recursive closures,
perhaps you should just use named functions / an anonymous class. I
don't find it useful to “optimize DX” for that case, since
self-recursive Closures are already somewhat questionable / rarely used
(though I think I've used that myself a handful of times in the past).
I don't disagree. To reveal my ulterior motive: References [1].
References are used relatively rarely by PHP programmers, but they
sneak their way into many more places in the engine than you might
expect. They are also often confusing for users, bad for the
optimizer, and make implementing some highly requested features (e.g.
typed arrays) much harder.
I started collecting a list of things that require references, and
self-recursive closures is one of them.
It may be an unrealistic goal, but if we ever want to even think about
deprecating references, we'll need stable alternatives for cases where
references are currently the only viable option.
Ilija
[1] https://docs.google.com/document/d/1DTi4DL6wLOpDhNlmtD-G4Xr-F9oq7cTG6irK7BPxllI/edit?usp=sharing
If you are at the point where you need mutually recursive closures,
perhaps you should just use named functions / an anonymous class. I
don't find it useful to “optimize DX” for that case, since
self-recursive Closures are already somewhat questionable / rarely used
(though I think I've used that myself a handful of times in the past).I don't disagree. To reveal my ulterior motive: References [1].
I don't disagree either - though maybe I tend to write more functionally
than is maybe idiomatic for PHP, so I'm already more comfortable with
both passing closures around and recursion. At the moment though,
use-by-reference is the mechanism with the smallest developer footprint.
Just pay attention to what you're storing in your local variables (you
don't have so many you're losing track, right?) and you won't get bitten.
As for other uses ... I can't find the last time I used & in foreach()
to modify an array's elements in-place (though I'm sure there are some
somewhere), I don't think I've ever created a function with a
by-reference parameter, and I can find precisely one instance where I
used it to abbreviate a long-winded multidimensional array lookup that I
would have otherwise had to write several times (including an assignment).
Hi
Am 2025-03-31 22:38, schrieb Ilija Tovilo:
Do you have thoughts or concerns with this solution? If not, I'm
planning to merge it into master in a few weeks.
I don't particularly like this kind of “magic scope / backtrace
introspecting” functions, but it certainly is a pragmatic solution (and
there's sufficient precedent, including Fiber::getCurrent()
).
Best regards
Tim Düsterhus