[VOTE] First-class callable syntax

php.internals

Nikita Popov

5 years ago
Hi internals, As the partial function application RFC has not been accepted, I have opened voting on https://wiki.php.net/rfc/first_class_callable_syntax. The vote closes on 2021-07-16. This RFC uses a syntax that is forward-compatible with partial function application. Should it not be accepted, I'll explore alternative syntax possibilities. Regards, Nikita

Ralph Schindler

5 years ago
> This RFC uses a syntax that is forward-compatible with partial function > application. Should it not be accepted, I'll explore alternative syntax > possibilities.
Given the choice of syntax in this proposal (which I do like)... Is the following (potentially future) use case precluded by the syntax (whether or not you like it immediately ;) )? Basically be able to: 1. be able to refer to an instance method statically (this is currently an error) 2. allow late binding of $this/context/closure scope, either via bindTo() or via Reflection setClosureThis() (does not exist) class MyController { public function myAction($name) { return new Response("Hello, $name"); } } // Closure with no $this (to be filled in later) $fn = MyController->myAction(...); $controller = new MyController; $response = ($fn->bindTo($controller))($params); -ralph

Unnamed Person

5 years ago
On Fri, Jul 2, 2021 at 10:32 AM Ralph Schindler <ralph@ralphschindler.com> wrote:
> > > > This RFC uses a syntax that is forward-compatible with partial function > > application. Should it not be accepted, I'll explore alternative syntax > > possibilities. > > Given the choice of syntax in this proposal (which I do like)... Is the > following (potentially future) use case precluded by the syntax (whether > or not you like it immediately ;) )? > > Basically be able to: > > 1. be able to refer to an instance method statically (this is currently > an error) > > 2. allow late binding of $this/context/closure scope, either via > bindTo() or via Reflection setClosureThis() (does not exist) > > > > class MyController { > public function myAction($name) { > return new Response("Hello, $name"); > } > } > > // Closure with no $this (to be filled in later) > $fn = MyController->myAction(...); > > > $controller = new MyController; > $response = ($fn->bindTo($controller))($params);
It would theoretically be: $fn = MyController::myAction(...); It currently errors:
> Fatal error: Uncaught Error: Non-static method MyController::myAction() cannot be called statically
I would be okay with allowing this, as long as it's bound before it's called.

Ralph Schindler

5 years ago
> It would theoretically be: > > $fn = MyController::myAction(...); > > It currently errors: > >> Fatal error: Uncaught Error: Non-static method MyController::myAction() cannot be called statically > > I would be okay with allowing this, as long as it's bound before it's called.
One other question regarding the current patch. I have not seen this aspect discussed yet. From the list in the first proposal section (https://wiki.php.net/rfc/first_class_callable_syntax#proposal), I am focusing this question on the few that are by name (strings).. so: strlen(...); Foo::method(...); $classStr::$methodStr(...); self::{$complex . $expression}(...); 'strlen'(...); [Foo::class, 'method'](...); Do these have to scan the target function and its signature in order to wrap them? In the cases of classes above, this will trigger the autoloader. I wonder if it could create a by-name reference that resolves the signature at just in time / at call time in these situations? In context, suppose I am suggesting that something like: $routes = [ '/my-action' => MyController::myAction(...), '/other-action => [OtherController::class, 'otherAction'](...), ... ]; would not invoke the autoloader until one of them may be actually used/called. This would be similar in philosophy as to how Foo::class creates a fully qualified string without confirming a class exists at that name when the scanner and executor sees ::class. -ralph

Nikita Popov

5 years ago
On Sat, Jul 3, 2021 at 3:51 PM Ralph Schindler <ralph@ralphschindler.com> wrote:
> > > It would theoretically be: > > > > $fn = MyController::myAction(...); > > > > It currently errors: > > > >> Fatal error: Uncaught Error: Non-static method MyController::myAction() > cannot be called statically > > > > I would be okay with allowing this, as long as it's bound before it's > called. > > > One other question regarding the current patch. I have not seen this > aspect discussed yet. > > From the list in the first proposal section > (https://wiki.php.net/rfc/first_class_callable_syntax#proposal), I am > focusing this question on the few that are by name (strings).. so: > > strlen(...); > Foo::method(...); > $classStr::$methodStr(...); > self::{$complex . $expression}(...); > 'strlen'(...); > [Foo::class, 'method'](...); > > Do these have to scan the target function and its signature in order to > wrap them? In the cases of classes above, this will trigger the > autoloader. I wonder if it could create a by-name reference that > resolves the signature at just in time / at call time in these situations? > > In context, suppose I am suggesting that something like: > > $routes = [ > '/my-action' => MyController::myAction(...), > '/other-action => [OtherController::class, 'otherAction'](...), > ... > ]; > > would not invoke the autoloader until one of them may be actually > used/called. > > This would be similar in philosophy as to how Foo::class creates a fully > qualified string without confirming a class exists at that name when the > scanner and executor sees ::class. >
No, something like this will definitely not be supported as part of this syntax. It is fundamentally at odds with this proposal, which requires that callability is validated at point of creation, rather than at point of call (both of these may differ). Regards, Nikita

Ralph Schindler

5 years ago
> > It would theoretically be: > > $fn = MyController::myAction(...); > > It currently errors: > >> Fatal error: Uncaught Error: Non-static method MyController::myAction() cannot be called statically > > I would be okay with allowing this, as long as it's bound before it's called.
Could we do this for 8.1? Does something of this scope need an RFC? I started working up a patch over the weekend, it is 1/2 done (only 7 failing tests). I would really like to see this behavior come in with the (...) syntax, as I see it as pretty valuable. -ralph

Nikita Popov

5 years ago
On Wed, Jul 7, 2021 at 10:41 PM Ralph Schindler <ralph@ralphschindler.com> wrote:
> > > > > > It would theoretically be: > > > > $fn = MyController::myAction(...); > > > > It currently errors: > > > >> Fatal error: Uncaught Error: Non-static method MyController::myAction() > cannot be called statically > > > > I would be okay with allowing this, as long as it's bound before it's > called. > > Could we do this for 8.1? Does something of this scope need an RFC? > > I started working up a patch over the weekend, it is 1/2 done (only 7 > failing tests). > > I would really like to see this behavior come in with the (...) syntax, > as I see it as pretty valuable. >
I'm rather strongly opposed to this. MyController::myAction(...) is not callable, ergo trying to create a callable from it will fail. As I said before, performing the callability check at time of construction is a core part of this proposal, that I am not willing to compromise on. Regards, Nikita

Unnamed Person

5 years ago
On Wed, Jul 7, 2021 at 2:40 PM Ralph Schindler <ralph@ralphschindler.com> wrote:
> > > > > > > It would theoretically be: > > > > $fn = MyController::myAction(...); > > > > It currently errors: > > > >> Fatal error: Uncaught Error: Non-static method MyController::myAction() cannot be called statically > > > > I would be okay with allowing this, as long as it's bound before it's called. > > Could we do this for 8.1? Does something of this scope need an RFC? > > I started working up a patch over the weekend, it is 1/2 done (only 7 > failing tests). > > I would really like to see this behavior come in with the (...) syntax, > as I see it as pretty valuable. > > -ralph
I think this requires an RFC. The behavior of `MyController::myAction(...)` should align with `Closure::fromCallable('MyController::myAction')`, and the latter is forbidden today. We should use an RFC to support the behavior in both places. We shouldn't try to sneak it in, especially as Nikita is opposed.

Sara Golemon

5 years ago
On Fri, Jul 2, 2021 at 5:51 AM Nikita Popov <nikita.ppv@gmail.com> wrote:
> As the partial function application RFC has not been accepted, I have > opened voting on https://wiki.php.net/rfc/first_class_callable_syntax. The > vote closes on 2021-07-16. > > This RFC uses a syntax that is forward-compatible with partial function > application. Should it not be accepted, I'll explore alternative syntax > possibilities. > >
Was replying to a comment on reddit which made me look closer at your implementation. It looks like this does NOT allow for use of function-like language constructs. e.g. $e = echo(...); $p = print(...); $r = require(...); // etc... 1/ Is this intentional? 2/ Is this actually a good thing? I'm actually of a mind that it's a good thing, as surprising ways to reach eval() are foot-guns waiting to happen, but wanted to get confirmation. -Sara

Nikita Popov

5 years ago
On Mon, Jul 5, 2021 at 4:14 PM Sara Golemon <pollita@php.net> wrote:
> On Fri, Jul 2, 2021 at 5:51 AM Nikita Popov <nikita.ppv@gmail.com> wrote: > >> As the partial function application RFC has not been accepted, I have >> opened voting on https://wiki.php.net/rfc/first_class_callable_syntax. >> The >> vote closes on 2021-07-16. >> >> This RFC uses a syntax that is forward-compatible with partial function >> application. Should it not be accepted, I'll explore alternative syntax >> possibilities. >> >> > Was replying to a comment on reddit which made me look closer at your > implementation. It looks like this does NOT allow for use of function-like > language constructs. > e.g. $e = echo(...); $p = print(...); $r = require(...); // etc... > > 1/ Is this intentional? >
Yes.
> 2/ Is this actually a good thing? >
Yes. echo is not a function, so you can't acquire a callable to it. The actual echo syntax is echo "Foo". PHP allows you to write echo("Foo") in the same way it allows you to write echo((((("Foo"))))). Don't do it :) Regards, Nikita

Rowan Collins

5 years ago
On 05/07/2021 15:35, Nikita Popov wrote:
> The actual echo syntax is echo "Foo". PHP allows you to write echo("Foo") > in the same way it allows you to write echo((((("Foo"))))). Don't do it :)
Indeed. Note that this is valid: echo "hello", " ", "world"; But this is not: echo("hello", " ", "world"); I have in the past had a bug make it into production because the parentheses around a call to "include" were mis-placed because of this incorrect belief that it is "function-like": if ( include('some_file.php') && bar() ) Is equivalent to this (will always run bar(), then attempt to include either (string)true or (string)false): if ( include ('some_file.php' && bar()) ) When what was intended was this: if ( (include 'some_file.php') && bar() ) Regards,
-- Rowan Tommins [IMSoP]

Nikita Popov

5 years ago
On Fri, Jul 2, 2021 at 12:50 PM Nikita Popov <nikita.ppv@gmail.com> wrote:
> Hi internals, > > As the partial function application RFC has not been accepted, I have > opened voting on https://wiki.php.net/rfc/first_class_callable_syntax. > The vote closes on 2021-07-16. >
The RFC has been accepted unanimously, with 44 votes in favor. Regards, Nikita