Hello internals,
For reasons, I was reviewing the conversation where adding closures to
PHP was added, and it reminded me that currently the only way for a
closure to call itself is slightly terribly, so I drafted an RFC:
https://wiki.php.net/rfc/closure_self_reference
Before I spend time on it, is there any strong reason why this is a bad idea?
cheers
Dan
Ack
Hello internals,
For reasons, I was reviewing the conversation where adding closures to
PHP was added, and it reminded me that currently the only way for a
closure to call itself is slightly terribly, so I drafted an RFC:https://wiki.php.net/rfc/closure_self_reference
Before I spend time on it, is there any strong reason why this is a bad idea?
cheers
Dan
Ack--
To unsubscribe, visit: https://www.php.net/unsub.php
Is there any way we can we spell it __FUNCTION__
?
something i'm missing from Javascript is the ability to give names to closures,
this both gives closures the ability to reference themselves, but it
also makes for meaningful stack traces, eg this is legal javascript:
(function TheClosuresLocalName(){console.log(TheClosuresLocalName);
throw new Error("look at my stacktrace!");})();
- the name is optional, and only visible inside the closure itself,
and unfortunately this is not legal in PHP, i wish it was.
Think the way it is implemented in JS is better than $lambda
variable. At
least it doesn't have BC breaks (does it?)
On Tue, Nov 10, 2020, 7:38 PM Hans Henrik Bergan divinity76@gmail.com
wrote:
something i'm missing from Javascript is the ability to give names to
closures,
this both gives closures the ability to reference themselves, but it
also makes for meaningful stack traces, eg this is legal javascript:(function TheClosuresLocalName(){console.log(TheClosuresLocalName);
throw new Error("look at my stacktrace!");})();
- the name is optional, and only visible inside the closure itself,
and unfortunately this is not legal in PHP, i wish it was.--
To unsubscribe, visit: https://www.php.net/unsub.php
something i'm missing from Javascript is the ability to give names to
closures, ...the name is optional, and only visible inside the closure
itself, and unfortunately this is not legal in PHP, i wish it was.
I really like that...but unfortunately that wouldn't work in PHP.
In JS, when a function is declared inside another function, the name
of it is limited to the scope of the containing function. In PHP, when
a function is declared inside another function, it is put into the
current namespace's global scope.
Changing how scope works in PHP would be too large a change for just this.
Levi Morrison wrote:
Is there any way we can spell it
__FUNCTION__
?
I think using some sort of constant, rather than a magic variable
sounds is probably the way to go. I would hope we could find something
better than that as:
return FUNCTION($n-1) + FUNCTION($n-2);
is pretty hard on my eyes.
I think I'll wander over to the 'Support for <func>::function syntax' thread...
cheers
Dan
Ack
PHP
function foo1() {
function bar() {}
}
function foo2() {
function bar() {}
}
foo1();
foo2();
// Fatal error: Cannot redeclare bar()
JS
function foo1() {
var fn = function TheClosuresLocalName(){
console.log("I am closure inside foo1");
}
return fn;
}
function foo2() {
var fn = function TheClosuresLocalName(){
console.log("I am closure inside foo2");
}
return fn;
}
fn1 = foo1();
fn2 = foo2();
fn1();
fn2();
"I am closure inside foo1"
"I am closure inside foo2"
something i'm missing from Javascript is the ability to give names to
closures, ...the name is optional, and only visible inside the closure
itself, and unfortunately this is not legal in PHP, i wish it was.I really like that...but unfortunately that wouldn't work in PHP.
In JS, when a function is declared inside another function, the name
of it is limited to the scope of the containing function. In PHP, when
a function is declared inside another function, it is put into the
current namespace's global scope.Changing how scope works in PHP would be too large a change for just this.
In JavaScript, a named function expression is different to a function
declaration:
var fn = function foo() {console.log('blah')}
foo()
=> Uncaught ReferenceError: foo is not defined
vs.
function foo() {console.log('blah')}
foo()
=> blah
So the named function expression is still an anonymous function; the
label is only defined inside of the function body.
Christoph
I think that introducing a new variable, that even uncommon, could cause BC.
My suggestion is to reuse the keyword as
to set a variable that will
represent its own closure. It is more flexible once that we could choose
any name and reuse in nested closures. It is pretty similar to how SQL
works too.
function fn1() as $lambda1 {
return function() as $lambda2 use ($lambda1) {
return [ gettype($lambda1), gettype($lambda2) ];
};
}
Atenciosamente,
David Rodrigues
Em qua., 11 de nov. de 2020 às 14:59, Christoph M. Becker cmbecker69@gmx.de
escreveu:
On Tue, 10 Nov 2020 at 17:39, Hans Henrik Bergan divinity76@gmail.com
wrote:something i'm missing from Javascript is the ability to give names to
closures, ...the name is optional, and only visible inside the closure
itself, and unfortunately this is not legal in PHP, i wish it was.I really like that...but unfortunately that wouldn't work in PHP.
In JS, when a function is declared inside another function, the name
of it is limited to the scope of the containing function. In PHP, when
a function is declared inside another function, it is put into the
current namespace's global scope.Changing how scope works in PHP would be too large a change for just
this.In JavaScript, a named function expression is different to a function
declaration:var fn = function foo() {console.log('blah')} foo() => Uncaught ReferenceError: foo is not defined
vs.
function foo() {console.log('blah')} foo() => blah
So the named function expression is still an anonymous function; the
label is only defined inside of the function body.Christoph
--
To unsubscribe, visit: https://www.php.net/unsub.php
In JavaScript, a named function expression is different to a function
declaration:var fn = function foo() {console.log('blah')} foo() => Uncaught ReferenceError: foo is not defined
vs.
function foo() {console.log('blah')} foo() => blah
So the named function expression is still an anonymous function; the
label is only defined inside of the function body.
I think the key difference is that in JS, functions and variables occupy
the same namespace, so "function foo" and "var foo = function" mean
roughly the same thing. For the same reason, inside a named function
expression, it acts as though there is a local variable with the given name:
var fn = function foo() {console.log(typeof foo)};
fn();
// outputs 'function'
which actually makes it very similar to...
My suggestion is to reuse the keyword
as
to set a variable that will
represent its own closure. It is more flexible once that we could choose
any name and reuse in nested closures. It is pretty similar to how SQL
works too.function fn1() as $lambda1 {
return function() as $lambda2 use ($lambda1) {
return [ gettype($lambda1), gettype($lambda2) ];
};
}
I like this idea; it avoids reserving a magic variable name, but gives
all the flexibility of having the full Closure object available (having
a "constant" whose value was a Closure object would be somewhat odd).
To take Dan's example from the RFC, this would no longer have any surprises:
$fibonacci = function (int $n) as $fibonacci {
if ($n === 0) return 0;
if ($n === 1) return 1;
return $fibonacci($n-1) + $fibonacci($n-2);
};
$this_is_the_original_fibonacci = $fibonacci;
$fibonacci = function (int $n) { return rand(0, $n); };
echo $this_is_the_original_fibonacci(10). "\n";
The $fibonacci inside the closure would be a normal local variable,
which just happens to have the same name as the one outside, so
assigning to one would have no effect on the other.
Regards,
--
Rowan Tommins (né Collins)
[IMSoP]
My suggestion is to reuse the keyword
as
to set a variable that will represent its own
closure. It is more flexible once that we could choose any name and reuse in nested closures.
It is pretty similar to how SQL works too.
Hi David,
That's a pretty reasonable suggestion, as it's less magic and less BC breaking.
I've updated the words of the RFC to that, but am even less sure of
how if/how that could be implemented...
Michael Voříšek wrote:
by allowing to specify/capture the variable after the function is assigned:
$f = function () use ($f) {...};
Unfortunately that's not how PHP works. The assignment to $f doesn't
happen until after the closure has been created. That's why currently
the variable can be passed in by reference....but that has the
undesirable problem that the RFC seeks to avoid.
cheers
Dan
Ack
On Wed, Nov 11, 2020 at 12:37 PM David Rodrigues david.proweb@gmail.com
wrote:
My suggestion is to reuse the keyword
as
to set a variable that will
represent its own closure. It is more flexible once that we could choose
any name and reuse in nested closures. It is pretty similar to how SQL
works too.function fn1() as $lambda1 {
return function() as $lambda2 use ($lambda1) {
return [ gettype($lambda1), gettype($lambda2) ];
};
}
My initial reaction to this is: Technically doable (easy even), but looks
a bit... ugly? weird? surprising?
I think my main objection to it is how much is now stacked after the
parameter list:
function($args...) : returnType as $local use ($captures...) {
...
};
That's a lot of... stuff (granted, we have most of it already) and how it
looks when stacked together gets funky.
Is it returnType as $thing
? That doesn't read well, but I don't see us
making return type movable. as/use should be placable in either order, but
maybe we give them required order anyway?
TLDR; Long winded way to say I'm not inherently against this, and I like
the idea of a lambda being able to recurse (not that I can ever recall
having use for it, but maybe I fell back on named functions for that), I
just think it's getting a bit wordy.
-Sara
Is it
returnType as $thing
? That doesn't read well, but I don't see
us making return type movable. as/use should be placable in either order,
but maybe we give them required order anyway?
I don't know if that would be a problem, because today we can have it
"function(): Type use($x)", so "Type use($x)"? I have to agree that it may
sound strange at first, but I also can't imagine many ways of doing things
differently without creating more complexities.
(1) function as $lambda (... $args): returnType use ($captures...) {}
(2) function (... $args) as $lambda: returnType use ($captures...) {}
(3) function (... $args): returnType as $lambda use ($captures...) {} //
original suggestion
(4) function (... $args): returnType use ($captures...) as $lambda {}
(4.b) function (... $args): returnType as $lambda {} // without use() will
be same as (3)
Or, define a "inline function" with its own name, visible only within the
scope itself. Similar to how JS works.
(5) function $lambda(... $args): returnType use ($captures...) {}
To be honest, as I typed what came to mind, I ended up preferring this last
option.
JS way:
function fb(n) {
if (n === 0) return 0;
if (n === 1) return 1;
return fb(n - 1) + fb(n - 2);
}
PHP (self-referenced) way:
function $fb($n): int {
if ($n === 0) return 0;
if ($n === 1) return 1;
return $fb($n - 1) + $fb($n - 2);
}
Currently PHP will accepts the same JS solution, but it will exposes fb()
to global scope and could causes errors:
function test() {
function fb($n): int
{
if ($n === 0) return 0;
if ($n === 1) return 1;
return fb($n - 1) + fb($n - 2);
}
var_dump(fb(10));
}
test();
test(); // Fatal error: Cannot redeclare fb()
Atenciosamente,
David Rodrigues
Em sex., 20 de nov. de 2020 às 12:14, Sara Golemon pollita@php.net
escreveu:
On Wed, Nov 11, 2020 at 12:37 PM David Rodrigues david.proweb@gmail.com
wrote:My suggestion is to reuse the keyword
as
to set a variable that will
represent its own closure. It is more flexible once that we could choose
any name and reuse in nested closures. It is pretty similar to how SQL
works too.function fn1() as $lambda1 {
return function() as $lambda2 use ($lambda1) {
return [ gettype($lambda1), gettype($lambda2) ];
};
}My initial reaction to this is: Technically doable (easy even), but looks
a bit... ugly? weird? surprising?I think my main objection to it is how much is now stacked after the
parameter list:function($args...) : returnType as $local use ($captures...) {
...
};That's a lot of... stuff (granted, we have most of it already) and how it
looks when stacked together gets funky.Is it
returnType as $thing
? That doesn't read well, but I don't see us
making return type movable. as/use should be placable in either order, but
maybe we give them required order anyway?TLDR; Long winded way to say I'm not inherently against this, and I like
the idea of a lambda being able to recurse (not that I can ever recall
having use for it, but maybe I fell back on named functions for that), I
just think it's getting a bit wordy.-Sara
Is it
returnType as $thing
? That doesn't read well, but I don't see
us making return type movable. as/use should be placable in either order,
but maybe we give them required order anyway?I don't know if that would be a problem, because today we can have it
"function(): Type use($x)", so "Type use($x)"? I have to agree that it may
sound strange at first, but I also can't imagine many ways of doing things
differently without creating more complexities.(1) function as $lambda (... $args): returnType use ($captures...) {}
(2) function (... $args) as $lambda: returnType use ($captures...) {}
(3) function (... $args): returnType as $lambda use ($captures...) {} //
original suggestion
(4) function (... $args): returnType use ($captures...) as $lambda {}
(4.b) function (... $args): returnType as $lambda {} // without use() will
be same as (3)Or, define a "inline function" with its own name, visible only within the
scope itself. Similar to how JS works.(5) function $lambda(... $args): returnType use ($captures...) {}
To be honest, as I typed what came to mind, I ended up preferring this last
option.
I kind of like that option, too. The main question in my mind is whether that means the same variable is used internally and externally or not. That is, my knee-jerk reaction when reading that was to ask if this would be confusing:
$fib = function $f(int $n): int { ... }
Viz, do $fib and $f need to match?
It looks from the example below like you're saying it's actually:
function $fib(int $n): int { ... }
Where $fib is then both the internal and external variable name.
That seems unexpected. It may be the best way to go about it, but it feels surprising as a user space developer.
I do think something like that is probably the best way forward, though.
The other question of course is short-lambdas. Would that mean this?
fn $fib(int $n): int => match($n) {
0 => 0,
1 => 1,
default => $fib(n - 1) + $fib($n - 2),
};
If the function is inlined, does that mean the variable name leaks and thus remains in scope (and the closure thus using memory)?
array_map(fn $fib(int $n): int => match($n) {
0 => 0,
1 => 1,
default => $fib(n - 1) + $fib($n - 2),
}, range(1, 6));
print $fib(5); // Does this work?
(No you shouldn't write it like that, but since Fibonacci is the example we're working with...)
--Larry Garfield
On Fri, Nov 20, 2020 at 11:40 AM Larry Garfield larry@garfieldtech.com
wrote:
(5) function $lambda(... $args): returnType use ($captures...) {}
To be honest, as I typed what came to mind, I ended up preferring this
last
option.I kind of like that option, too.
Agreed that this has some attractiveness to it.
What does returning by reference look like in that world though?
$fn = function &$lambda($args...) {...};
Capturing lambda by reference doesn't make sense when you've just declared
the var, but it looks confusing AF.
$fn = function $lambda&($args...) {...};
Hello PERL...
The main question in my mind is whether that means the same variable is
used internally and externally or not.
The prototype I wrote in my brain for this would have $lambda exclusively
within the closure's scope, not leaked to the declaring scope. I think I
prefer that even though it looks a bit odd to have a name on a function
that's not accessible from where it's defined.
TL;DR - Still a little confusing.
-Sara
On Fri, Nov 20, 2020 at 5:02 PM David Rodrigues david.proweb@gmail.com
wrote:
Is it
returnType as $thing
? That doesn't read well, but I don't see
us making return type movable. as/use should be placable in either order,
but maybe we give them required order anyway?I don't know if that would be a problem, because today we can have it
"function(): Type use($x)", so "Type use($x)"?
No we can't, try it (Parse error). The correct order is
"function() use($x): Type".
--
Guilliam Xavier
On Fri, Nov 20, 2020 at 12:58 PM Guilliam Xavier guilliam.xavier@gmail.com
wrote:
I don't know if that would be a problem, because today we can have it
"function(): Type use($x)", so "Type use($x)"?No we can't, try it (Parse error). The correct order is
"function() use($x): Type".
Ugh. That's ugly. I want to say we should fix it, but the horse has left
the stables, so it's too late to enforce the right (to me) ordering.
-Sara
No we can't, try it (Parse error). The correct order is
"function() use($x): Type".
Oh! You are right!
In this case, we still have the old idea:
function() as $lambda use ($x): int {}
Ugh. That's ugly. I want to say we should fix it, but the horse has left
the stables, so it's too late to enforce the right (to me) ordering.
Yeah, but is there any way to go back now? :/
Atenciosamente,
David Rodrigues
Em sex., 20 de nov. de 2020 às 16:08, Sara Golemon pollita@php.net
escreveu:
On Fri, Nov 20, 2020 at 12:58 PM Guilliam Xavier <
guilliam.xavier@gmail.com> wrote:I don't know if that would be a problem, because today we can have it
"function(): Type use($x)", so "Type use($x)"?No we can't, try it (Parse error). The correct order is
"function() use($x): Type".Ugh. That's ugly. I want to say we should fix it, but the horse has left
the stables, so it's too late to enforce the right (to me) ordering.-Sara
Sara Golemon wrote:
looks a bit... ugly? weird? surprising?
I had the same initial reaction, and was going to tell David what I
thought of the suggestion. But after a couple of days, I was unable to
actually articulate what I didn't like it, so think I was just having
a gut reaction to something I hadn't seen before, and so wasn't used
to seeing.
Sara Golemon wrote:
That's a lot of... stuff
Yeah, but it's a really rare use-case for a closure to need to be able
to reference itself, so I think it's probably fine to have some
verbosity around "this closure is doing something quite unusual".
I personally think any of:
function (... $args) as $lambda: returnType use ($captures...) {}
function (... $args): returnType as $lambda use ($captures...) {}
function (... $args): returnType use ($captures...) as $lambda {}
would be fine and various 'my aesthetic choice is objectively the
best' arguments, could be made for any of them.
David Rodrigues wrote:
I ended up preferring this last option.
I prefer the others as I think they are more deliberate syntax (i.e.
don't look like a typo), avoid confusion around why can't normal
functions be declared with leading $'s, and almost certainly would be
easier to search for what they are. e.g. "php closure as variable"
cheers
Dan
Ack
Hi everyone,
maybe a bad idea, but what about addressing the "Principle of least
astonishment" issue by allowing to specify/capture the variable after
the function is assigned:
$f = function () use ($f) {...};
With kind regards / Mit freundlichen Grüßen / S přátelským pozdravem,
Michael Voříšek
Hello internals,
For reasons, I was reviewing the conversation where adding closures to
PHP was added, and it reminded me that currently the only way for a
closure to call itself is slightly terribly, so I drafted an RFC:https://wiki.php.net/rfc/closure_self_reference
Before I spend time on it, is there any strong reason why this is a bad idea?
cheers
Dan
Ack
if i have understood the "as"-suggestion correctly:
$fn = function() as $lambdaOrAnyName {var_dump($lambdaOrAnyName);};
(function() as $lambdaOrAnyName{var_dump($lambdaOrAnyName);})();
then that syntax is fine with me. this is also very close to how it
works in JavaScript (except the "as" part),
i like the as-suggestion better than the $lambda suggestion in current
RFC-draft,
it has 0 bc break and people who want to call it $lambda can simply do
function() as $lamda{};