Hi
Volker and I would like to start discussion on our RFC to "Support
Closures in constant expressions".
Please find the following resources for your reference:
- RFC: https://wiki.php.net/rfc/closures_in_const_expr
- Implementation: https://github.com/php/php-src/pull/16458
Best regards
Tim Düsterhus
On Tuesday, 29 October 2024 at 18:14, Tim Düsterhus tim@bastelstu.be
wrote:
Hi
Volker and I would like to start discussion on our RFC to "Support
Closures in constant expressions".Please find the following resources for your reference:
- RFC: https://wiki.php.net/rfc/closures_in_const_expr
- Implementation: https://github.com/php/php-src/pull/16458
Best regards
Tim Düsterhus
Hi, Tim!
Looking forward to this feature! Good luck with the RFC.
Will the first class callable syntax be supported?
#[Serialize\Custom(strtoupper(...))]
--
Valentin
Hi
Am 2024-10-29 21:12, schrieb Valentin Udaltsov:
Will the first class callable syntax be supported?
#[Serialize\Custom(strtoupper(...))]
It is currently listed in the “Future Scope” section. Mainly, because
the implementation does not trivially extend to first-class callables.
When you have a regular Closure, everything you need to know is right
inside the file. For first-class callables the target function might
reside in a different file and already needs to be available. For static
methods, the autoloader might need to be called. So first-class
callables behave similarly to new-expressions, whereas Closures behave
more like string literals or number literals.
Best regards
Tim Düsterhus
Hi
Volker and I would like to start discussion on our RFC to "Support
Closures in constant expressions".Please find the following resources for your reference:
- RFC: https://wiki.php.net/rfc/closures_in_const_expr
- Implementation: https://github.com/php/php-src/pull/16458
Best regards
Tim Düsterhus
This seems like a good idea to me. My only real question is why we need to forbid short-closures. I fully agree that capturing variables for such functions doesn't work. What I don't understand is why that precludes short-closures. Is it not possible to "just" say "there's nothing to even capture in this context, don't try"? (There may be technical reasons for that, but I do not know what they are and the RFC doesn't say.)
--Larry Garfield
Hi
Am 2024-10-30 05:25, schrieb Larry Garfield:
This seems like a good idea to me. My only real question is why we
need to forbid short-closures. I fully agree that capturing variables
for such functions doesn't work. What I don't understand is why that
precludes short-closures. Is it not possible to "just" say "there's
nothing to even capture in this context, don't try"? (There may be
technical reasons for that, but I do not know what they are and the RFC
doesn't say.)
It would indeed require some special handling to disable the
auto-capturing in the code. This would be solvable of course, but
there's also semantic ambiguity, because users reasonably expect short
closures to perform auto-capturing:
<?php
$foo = 'foo';
const Closure = static fn (array $bar): array => [$foo, $bar];
var_dump((Closure)('bar'));
If this would be legal syntax: What would you expect to be printed?
Best regards
Tim Düsterhus
Hi
Am 2024-10-30 05:25, schrieb Larry Garfield:
This seems like a good idea to me. My only real question is why we
need to forbid short-closures. I fully agree that capturing variables
for such functions doesn't work. What I don't understand is why that
precludes short-closures. Is it not possible to "just" say "there's
nothing to even capture in this context, don't try"? (There may be
technical reasons for that, but I do not know what they are and the RFC
doesn't say.)It would indeed require some special handling to disable the
auto-capturing in the code. This would be solvable of course, but
there's also semantic ambiguity, because users reasonably expect short
closures to perform auto-capturing:
Hi Tim,
So, why not allow capturing, since anyway the only place to capture are
constants and static variables?
And this way we could have short closures with auto-capture.
If there are some technical reasons for not doing that, can we have written
in the RFC?
Alex
Hi Tim,
So, why not allow capturing, since anyway the only place to capture are
constants and static variables?
And this way we could have short closures with auto-capture.
If there are some technical reasons for not doing that, can we
have written in the RFC?Alex
You don't need to capture constants or statics at all since they're all
globally accessible (Well, except protected/private ones but that would
require assuming a scope at compile time which is probably a bad idea)
Hi
Am 2024-10-30 09:31, schrieb Alexandru Pătrănescu:
So, why not allow capturing, since anyway the only place to capture are
constants and static variables?
Static variables already support arbitrary expressions since
https://wiki.php.net/rfc/arbitrary_static_variable_initializers. The RFC
initially mentioned static variables as a place where only const-expr
would be accepted, but I removed that once I remembered about that other
RFC.
And this way we could have short closures with auto-capture.
If there are some technical reasons for not doing that, can we have
written
in the RFC?
As stated in the RFC: This constraint is consistent with not supporting
variables in constant expressions. Capturing a variable is effectively
the same as reading a variable. It makes no sense to support one, but
not the other.
Best regards
Tim Düsterhus
Hi Tim
śr., 30 paź 2024 o 09:02 Tim Düsterhus tim@bastelstu.be napisał(a):
Hi
Am 2024-10-30 05:25, schrieb Larry Garfield:
This seems like a good idea to me. My only real question is why we
need to forbid short-closures. I fully agree that capturing variables
for such functions doesn't work. What I don't understand is why that
precludes short-closures. Is it not possible to "just" say "there's
nothing to even capture in this context, don't try"? (There may be
technical reasons for that, but I do not know what they are and the RFC
doesn't say.)It would indeed require some special handling to disable the
auto-capturing in the code. This would be solvable of course, but
there's also semantic ambiguity, because users reasonably expect short
closures to perform auto-capturing:<?php $foo = 'foo'; const Closure = static fn (array $bar): array => [$foo, $bar];
Personally, I'd expect short-closures be available as well, did you though
about maybe reusing const
token instead of static
to make the
information visually available for users?
I mean if PHP could parse const Closure = const fn (array $bar): array => [$foo, $bar];
and the documentation tells that const
modifier prevent
auto-capture in case of short-closures but also in case of normal closures
to prevent use of use() clause that would be visible instantly in the code.
Consider this syntax as well:
<?php
$foo = '';
const Closure = const function (array $bar): array { return [$foo, $bar]; }
This way we could also forbid use of global-variables, use() clause and
maybe even static $baz = 1
inside functions. So the closures better match
constant semantics. WDYT?
The engine could create an instance of let's say \ConstClosure
that would
be a subtype of \Closure
so it still can be used as a normal closure but
not the other way. Effectively this would make these closures
pure-functions if am not wrong here.
Overall I like the idea.
Cheers,
Michał Marcin Brzuchalski
Hi
Am 2024-10-30 10:58, schrieb Michał Marcin Brzuchalski:
Personally, I'd expect short-closures be available as well, did you
though
about maybe reusingconst
token instead ofstatic
to make the
information visually available for users?
We do not believe the cost-benefit ratio is worth it to introduce new
syntax and an entirely new language concept.
Best regards
Tim Düsterhus
Hi
Am 2024-10-30 05:25, schrieb Larry Garfield:
This seems like a good idea to me. My only real question is why we
need to forbid short-closures. I fully agree that capturing variables
for such functions doesn't work. What I don't understand is why that
precludes short-closures. Is it not possible to "just" say "there's
nothing to even capture in this context, don't try"? (There may be
technical reasons for that, but I do not know what they are and the RFC
doesn't say.)It would indeed require some special handling to disable the
auto-capturing in the code. This would be solvable of course, but
there's also semantic ambiguity, because users reasonably expect short
closures to perform auto-capturing:<?php $foo = 'foo'; const Closure = static fn (array $bar): array => [$foo, $bar]; var_dump((Closure)('bar'));
If this would be legal syntax: What would you expect to be printed?
Best regards
Tim Düsterhus
Hm. It would never occur to me to use a function for a non-class constant in the first place, so I don't know. :-) Frankly I can't recall the last time I used a non-class constant period. :-)
That said, PHP consts are already a bit squishy, and auto-capture is always by value. That wouldn't give us a loophole?
If it cannot reasonably be done now, but is possible, that should be listed in future scope with roughly what it would take for a follow-up to do. (And then we can argue if it should just be done now, with more information as to how much work that is.)
--Larry Garfield
Hi
Am 2024-10-30 05:25, schrieb Larry Garfield:
This seems like a good idea to me. My only real question is why we
need to forbid short-closures. I fully agree that capturing variables
for such functions doesn't work. What I don't understand is why that
precludes short-closures. Is it not possible to "just" say "there's
nothing to even capture in this context, don't try"? (There may be
technical reasons for that, but I do not know what they are and the RFC
doesn't say.)It would indeed require some special handling to disable the
auto-capturing in the code. This would be solvable of course, but
there's also semantic ambiguity, because users reasonably expect short
closures to perform auto-capturing:<?php $foo = 'foo'; const Closure = static fn (array $bar): array => [$foo, $bar]; var_dump((Closure)('bar'));
If this would be legal syntax: What would you expect to be printed?
Best regards
Tim DüsterhusHm. It would never occur to me to use a function for a non-class constant in the first place, so I don't know. :-) Frankly I can't recall the last time I used a non-class constant period. :-)
That said, PHP consts are already a bit squishy, and auto-capture is always by value. That wouldn't give us a loophole?
If it cannot reasonably be done now, but is possible, that should be listed in future scope with roughly what it would take for a follow-up to do. (And then we can argue if it should just be done now, with more information as to how much work that is.)
--Larry Garfield
To be honest, I thought it was a rhetorical question since the example is a runtime error (passing string to a function that takes an array), but on this note, we already know how it should behave: https://3v4l.org/RC6b3 because, as you said, it is captured by value. In theory, to support this, we would probably need “late binding constants” or constants that are executed just before any userland code is run. Similar to just emulating it yourself: https://3v4l.org/PMY4W
IMHO, the rules around constant expressions are not sound and feel arbitrary when you run into them. They are too easy to work around (though cumbersome) and the code still works, which seems to prove their arbitrary nature.
— Rob
To be honest, I thought it was a rhetorical question since the example is a runtime error (passing string to a function that takes an array), but on this note, we already know how it should behave: https://3v4l.org/RC6b3 because, as you said, it is captured by value. In theory, to support this, we would probably need “late binding constants” or constants that are executed just before any userland code is run. Similar to just emulating it yourself: https://3v4l.org/PMY4W
IMHO, the rules around constant expressions are not sound and feel arbitrary when you run into them. They are too easy to work around (though cumbersome) and the code still works, which seems to prove their arbitrary nature.
define()
does not produce "true" constants and has always allowed to define constants at run-time where there is more information available.
const expressions have always been about determining and defining constants at compile time so they can be put in SHM.
Which is why the const keyword exist.
(and yes I know define()
expression that are const expression are "true compile time constants")
Best regards,
Gina P. Banyard
To be honest, I thought it was a rhetorical question since the example is a runtime error (passing string to a function that takes an array), but on this note, we already know how it should behave: https://3v4l.org/RC6b3 because, as you said, it is captured by value. In theory, to support this, we would probably need “late binding constants” or constants that are executed just before any userland code is run. Similar to just emulating it yourself: https://3v4l.org/PMY4W
IMHO, the rules around constant expressions are not sound and feel arbitrary when you run into them. They are too easy to work around (though cumbersome) and the code still works, which seems to prove their arbitrary nature.
define()
does not produce "true" constants and has always allowed to define constants at run-time where there is more information available.const expressions have always been about determining and defining constants at compile time so they can be put in SHM.
Which is why the const keyword exist.
(and yes I knowdefine()
expression that are const expression are "true compile time constants")Best regards,
Gina P. Banyard
Hey Gina,
I was mostly referring to the comment that we can't define the expected behavior, not necessarily what is a "true constant" or not.
— Rob
Hi
Am 2024-10-31 08:22, schrieb Rob Landers:
To be honest, I thought it was a rhetorical question since the example
is a runtime error (passing string to a function that takes an array),
That clearly was a typo / copy-paste mistake / whatever you would like
to call it.
but on this note, we already know how it should behave:
https://3v4l.org/RC6b3 because, as you said, it is captured by value
The context was “can't we just pretend there’s nothing to capture in
const-expr contexts”. The example was intended to point out how that
will lead to semantic sadness.
In theory, to support this, we would probably need “late binding
constants” or constants that are executed just before any userland code
is run. Similar to just emulating it yourself: https://3v4l.org/PMY4W
Yes, but see also my new reply to Larry (and the reply to Alex).
Best regards
Tim Düsterhus
Hi
Am 2024-10-31 07:16, schrieb Larry Garfield:
Hm. It would never occur to me to use a function for a non-class
constant in the first place, so I don't know. :-) Frankly I can't
recall the last time I used a non-class constant period. :-)That said, PHP consts are already a bit squishy, and auto-capture is
always by value. That wouldn't give us a loophole?
Here's another brainteaser:
function foo(
string $bar,
Closure $baz = static fn () => $bar,
) {
var_dump($baz());
}
foo('captured');
What would you expect the semantics of that script to be?
If it cannot reasonably be done now, but is possible, that should be
listed in future scope with roughly what it would take for a follow-up
to do. (And then we can argue if it should just be done now, with more
information as to how much work that is.)
I have added an entry to the “Future Scope” section. See also my reply
to Alex.
Best regards
Tim Düsterhus
Hi
Am 2024-10-31 07:16, schrieb Larry Garfield:
Hm. It would never occur to me to use a function for a non-class
constant in the first place, so I don't know. :-) Frankly I can't
recall the last time I used a non-class constant period. :-)That said, PHP consts are already a bit squishy, and auto-capture is
always by value. That wouldn't give us a loophole?Here's another brainteaser:
function foo( string $bar, Closure $baz = static fn () => $bar, ) { var_dump($baz()); } foo('captured');
What would you expect the semantics of that script to be?
Isn't this semantically equivalent to:
function foo(
string $bar,
Closure $baz,
) {
$baz = static fn () => $bar;
var_dump($baz());
}
foo('captured');
— Rob
Hi
Am 2024-11-04 13:33, schrieb Rob Landers:
What would you expect the semantics of that script to be?
Isn't this semantically equivalent to:
Perhaps? That's the question I am asking. Given that this is not a valid
PHP program as of now, it would as least be debatable how this is
supposed to work. It gets funnier once you want to support variables in
the general case (which I consider a necessary companion to support
variable capturing):
class Foo {
public function __construct(&$out) {
$out = 'out';
}
}
function foo(
string $bar,
string $baz = new Foo($bar),
) {
var_dump($bar, $baz);
}
To be clear: We do not intend to solve this question with this RFC.
These examples are just intended to highlight the (semantic)
complexities of supporting variables within const-expr.
Best regards
Tim Düsterhus
Hi
Am 2024-10-31 07:16, schrieb Larry Garfield:
Hm. It would never occur to me to use a function for a non-class
constant in the first place, so I don't know. :-) Frankly I can't
recall the last time I used a non-class constant period. :-)That said, PHP consts are already a bit squishy, and auto-capture is
always by value. That wouldn't give us a loophole?Here's another brainteaser:
function foo( string $bar, Closure $baz = static fn () => $bar, ) { var_dump($baz()); } foo('captured');
What would you expect the semantics of that script to be?
My expectation is that it's confusing, and therefore we should simply disallow it. Which roughly translates to detecting if a closure tries to use an unbound variable statically. Which is probably more difficult than I make it sound. :-)
If it cannot reasonably be done now, but is possible, that should be
listed in future scope with roughly what it would take for a follow-up
to do. (And then we can argue if it should just be done now, with more
information as to how much work that is.)I have added an entry to the “Future Scope” section. See also my reply
to Alex.Best regards
Tim Düsterhus
Thanks. Can you include what the implementation challenges are for the future-scope items, to explain why they're being punted to later rather than addressed now? (As there seems clear interest in having all of them.)
--Larry Garfield
Here's another brainteaser:
function foo(
string $bar,
Closure $baz = static fn () => $bar,
) {
var_dump($baz());
}foo('captured');
What would you expect the semantics of that script to be?
My expectation is that it's confusing, and therefore we should simply disallow it. Which roughly translates to detecting if a closure tries to use an unbound variable statically. Which is probably more difficult than I make it sound. :-)
Which is why short closures are being disallowed.
The value of it is limited, and the semantics of it are confusing.
I am struggling to see why this is such a big deal.
You know very well that PHP has tricky semantics that make figuring out "is this case ok but not this" near impossible.
Moreover, having short closure work in some but not all cases is also just hella confusing, banning it for all is clear and easy to reason about.
If it cannot reasonably be done now, but is possible, that should be
listed in future scope with roughly what it would take for a follow-up
to do. (And then we can argue if it should just be done now, with more
information as to how much work that is.)I have added an entry to the “Future Scope” section. See also my reply
to Alex.
Thanks. Can you include what the implementation challenges are for the future-scope items, to explain why they're being punted to later rather than addressed now? (As there seems clear interest in having all of them.)
I have never seen a future scope section which describes these challenges, and this feels just giving more work to people for no benefit?
There has been clear intent on having Pattern matching and ADTs, yet they were split out because they are different scopes.
It is fine to have smaller incremental RFCs that build on top of each other without needing to provide justification as to why it was punted to later.
Arguably, the onus should be on people wanting it to be in the RFC to do the research and provide some implementation idea to have them be one RFC for this discussion to be at all reasonable.
Best regards,
Gina P. Banyard
On Monday, 4 November 2024 at 20:32, Larry Garfield
larry@garfieldtech.com wrote:Here's another brainteaser:
function foo(
string $bar,
Closure $baz = static fn () => $bar,
) {
var_dump($baz());
}foo('captured');
What would you expect the semantics of that script to be?
My expectation is that it's confusing, and therefore we should simply disallow it. Which roughly translates to detecting if a closure tries to use an unbound variable statically. Which is probably more difficult than I make it sound. :-)
Which is why short closures are being disallowed.
The value of it is limited, and the semantics of it are confusing.
I am struggling to see why this is such a big deal.
I suspect, since several people have asked about it, it's because most of the use cases for having a default Closure value are going to be short; the sort of short cases that short-closures and FCC are designed for. If it's long, then you're probably better off moving it to a private method and making a FCC reference to that private method the default value instead.
So if most of the use cases align with where one would use short-closures and FCC, not being able to is disappointing, even if the reasons for it are valid.
If it cannot reasonably be done now, but is possible, that should be
listed in future scope with roughly what it would take for a follow-up
to do. (And then we can argue if it should just be done now, with more
information as to how much work that is.)I have added an entry to the “Future Scope” section. See also my reply
to Alex.Thanks. Can you include what the implementation challenges are for the future-scope items, to explain why they're being punted to later rather than addressed now? (As there seems clear interest in having all of them.)
I have never seen a future scope section which describes these
challenges, and this feels just giving more work to people for no
benefit?
I'm not asking for a detailed description of the engine nuances of detecting unbound variables. Just something along the lines of "this would require detecting unbound variables, that's hard, so we'll save that for later." Which is a level of detail I frequently do provide in my RFC future-scopes.
--Larry Garfield
Hi
Am 2024-11-05 18:49, schrieb Larry Garfield:
My expectation is that it's confusing, and therefore we should simply
disallow it. Which roughly translates to detecting if a closure tries
to use an unbound variable statically. Which is probably more
difficult than I make it sound. :-)Which is why short closures are being disallowed.
The value of it is limited, and the semantics of it are confusing.
I am struggling to see why this is such a big deal.I suspect, since several people have asked about it, it's because most
of the use cases for having a default Closure value are going to be
short; the sort of short cases that short-closures and FCC are designed
for. If it's long, then you're probably better off moving it to a
private method and making a FCC reference to that private method the
default value instead.
Even though the initial example is one that uses a default parameter
value, we see the primary use case in supporting Closures in attribute
parameters. That’s why that’s the use case mentioned in the first
paragraph and that’s why all the snippets in the “Use Cases” section
showcase attributes.
I'm not asking for a detailed description of the engine nuances of
detecting unbound variables. Just something along the lines of "this
would require detecting unbound variables, that's hard, so we'll save
that for later." Which is a level of detail I frequently do provide in
my RFC future-scopes.
The extent of investigation I did regarding the implementation was
verifying that “yes, it indeed does crash”, because you can’t build an
implementation if you do not know what the expected behavior should be
in the first place. It is not at all obvious how variable capturing (or
rather: variables in general) should work from a semantic perspective,
as you confirmed yourself (“it’s confusing, and therefore we should
simply disallow it” [1]). We disagree with adding another special-case
to PHP’s semantics by introducing a “non-capturing short closure” as a
third type of Closure [2] and figuring out what the semantics of
variables within const-expr should be is out of scope for this RFC.
In any case, the “Constraints” section already explains why those
constraints exist.
Best regards
Tim Düsterhus
[1] My personal expectation for parameter default values would be that
any previous parameter would be legal to access and capture.
[2] Or fourth, if you consider FCC to be a type of Closure.
Hi
Volker and I would like to start discussion on our RFC to "Support
Closures in constant expressions".Please find the following resources for your reference:
- RFC: https://wiki.php.net/rfc/closures_in_const_expr
- Implementation: https://github.com/php/php-src/pull/16458
Best regards
Tim Düsterhus
Hi Tim,
This looks interesting, but I notice it says that the closure can access “private properties”:
This means that Closures in property default values may access private properties, methods, and class constants of the class where they are defined
I assume this means private static properties since it couldn’t be attached/bound to a specific instance? (Must be static?)
— Rob
Hi
Am 2024-10-30 13:06, schrieb Rob Landers:
This looks interesting, but I notice it says that the closure can
access “private properties”:This means that Closures in property default values may access private
properties, methods, and class constants of the class where they are
definedI assume this means private static properties since it couldn’t be
attached/bound to a specific instance? (Must be static?)
No: All properties. An object of the class could be passed as a
parameter to the Closure and then the Closure would be able to access
private properties of that object.
See Zend/tests/closure_const_expr/property_initializer_scope_001.phpt
in the PR for an example.
Best regards
Tim Düsterhus
Hi
Volker and I would like to start discussion on our RFC to "Support
Closures in constant expressions".Please find the following resources for your reference:
- RFC: https://wiki.php.net/rfc/closures_in_const_expr
- Implementation: https://github.com/php/php-src/pull/16458
Best regards
Tim Düsterhus
I very much like this feature, a bit sad that it doesn't support First Class Callable syntax, but I understand the increased complexity this would add.
Especially as supporting it would remove the last good reason to keep the callable type, which is to define a list of callables statically.
Best regards,
Gina P. Banyard
Hi
Am 2024-10-31 15:16, schrieb Gina P. Banyard:
I very much like this feature, a bit sad that it doesn't support First
Class Callable syntax, but I understand the increased complexity this
would add.
We hope to follow-up with a separate RFC for that, once we figured out
the technical implementation and thus the necessary constraints (if any)
that will need to be applied.
Best regards
Tim Düsterhus
Hi
Am 2024-10-29 16:11, schrieb Tim Düsterhus:
Volker and I would like to start discussion on our RFC to "Support
Closures in constant expressions".Please find the following resources for your reference:
- RFC: https://wiki.php.net/rfc/closures_in_const_expr
- Implementation: https://github.com/php/php-src/pull/16458
The minimum 14 days of discussion will have passed in roughly 26 hours.
Given that the discussion mostly revolved around the details of not
supporting short closures and the RFC proposal has been unchanged except
for some minor clarifications, we believe that everything important has
been said and thus plan to open the vote some time on Wednesday,
November 13.
Best regards
Tim Düsterhus