Introduce the abiltiy to use the first-call-callable syntax on non-static methods, statically

php.internals

Ollie Read

3 years ago
Hello all, I've created a feature request issue on GitHub (here: https://github.com/php/php-src/issues/10414), but I have been advised that it's best to post here. What I would like to introduce/suggest, is the ability to create a closure from a method using the first-class-callable syntax (eg: MyClass::aMethod(...)), for a non-static method, statically. Currently, the following code causes an error. ``` class Test { public function test(): string { return 'test'; } } $closure = Test::test(...); ``` I understand why the error is thrown, but, and I'm unsure of the specifics regarding this, I think we could delay the error until the closure was called. The reason for this, is that closures can be bound, so if you followed on from the code above, you could do the following: ``` $closure->bindTo(new Test); $closure(); ``` The above would bind the closure in $closure to the scope of an object, which in this case, is the class that the method belongs to. The best example I can think, for this, would be when filter a collection of instances. If you were using a collection library, you would currently have something like the following: ``` $collection->filter(function (Str $string) { return !$string->empty(); }); ``` Whereas it would be much nicer to have the following: ``` $collection->filter(Str::empty(...)); ``` In this situation, the collection library would be responsible for binding the closure to the value it is iterating. I have limited experience with PHPs source, and C in general, but my understanding would be that if we were creating a closure, we would skip the check for the static method. The code responsible for handling the closure call would most require some additional functionality to check if it was bound to a valid instance, returning an error if it isn't, and then returning an error if it isn't bound at all and the method isn't static. The more I think about it, the more I think this may require a new type of Closure, or at least a runtime applied interface, to help developers determine whether a closure was created using first-class-callable syntax. I'm interested in feedback on this, and if anyone could point me towards the correct part of the codebase that handles this, I can investigate further. I am happy to create an RFC if others think this viable. --- Best Regards, *Ollie Read*

Larry Garfield

3 years ago
On Sun, Jan 22, 2023, at 11:45 AM, Ollie Read wrote:
> Hello all, > > I've created a feature request issue on GitHub (here: > https://github.com/php/php-src/issues/10414), but I have been advised > that it's best to post here. > > What I would like to introduce/suggest, is the ability to create a > closure from a method using the first-class-callable syntax (eg: > MyClass::aMethod(...)), for a non-static method, statically. > > Currently, the following code causes an error. > > ``` > class Test { > public function test(): string { return 'test'; } > } > > $closure = Test::test(...); > ``` > > I understand why the error is thrown, but, and I'm unsure of the > specifics regarding this, I think we could delay the error until the > closure was called. The reason for this, is that closures can be bound, > so if you followed on from the code above, you could do the following: > > ``` > $closure->bindTo(new Test); > $closure(); > ``` > > The above would bind the closure in $closure to the scope of an object, > which in this case, is the class that the method belongs to. > > The best example I can think, for this, would be when filter a > collection of instances. If you were using a collection library, you > would currently have something like the following: > > ``` > $collection->filter(function (Str $string) { > return !$string->empty(); > }); > ``` > > Whereas it would be much nicer to have the following: > > ``` > $collection->filter(Str::empty(...)); > ``` > > In this situation, the collection library would be responsible for > binding the closure to the value it is iterating.
So you'd implement this yourself elsewhere? class Str { public function empty(): bool { ... } } I don't see in this example how this is any better than what is already currently possible: class Str { public static function empty(Str $s): bool { ... } } $collection->filter(Str::empty(...));
> I have limited experience with PHPs source, and C in general, but my > understanding would be that if we were creating a closure, we would > skip the check for the static method. The code responsible for handling > the closure call would most require some additional functionality to > check if it was bound to a valid instance, returning an error if it > isn't, and then returning an error if it isn't bound at all and the > method isn't static. > > The more I think about it, the more I think this may require a new type > of Closure, or at least a runtime applied interface, to help developers > determine whether a closure was created using first-class-callable > syntax.
This is, I think, the important part here, and would be a prerequisite. Right now there's no way (as far as I know) to differentiate a closure that is callable from one that would be callable if it were bound to an object. That's generally not a huge deal in practice as unbound closures are not often used, but what you're suggesting would make them much more likely. Also, a static closure cannot be bound, so you cannot just blindly bind whatever callable you're passed to $this, in your example. (Besides, blindly binding a closure to $this sounds like a great security hole.) So for some variant of this to work, I think you'd first need to think through how to (easily and without dipping into reflection) determine if a closure object is bindable (static or not) and if it's already bound. Once that's figured out, then we can see what, if any, short-hand way to make a not-yet-bound closure makes sense. (Which could be FCC syntax or not, I don't know.) --Larry Garfield

Ollie Read

3 years ago
You are absolutely correct. I guess the solution would be to handle it differently in this case. Creating a closure from a static method would be fine, as it creates a static closure, but when attempting to create a static closure from a non-static method, it would instead return a closure that errors if it isn't bound to an appropriate object. You'd most likely want to restrict this to public methods only, which would help with the security issues. There's already a check there that throws an error, so we can already tell the difference there, but the tricky part will be in the returned closure. Perhaps something like "BindingClosure" that throws the static error when attempting to call it unbound, or better yet, a more descriptive error about it requiring binding. Would that be feasible? On Sun, Jan 22, 2023, at 8:36 PM, Larry Garfield wrote:
> On Sun, Jan 22, 2023, at 11:45 AM, Ollie Read wrote: > > Hello all, > > > > I've created a feature request issue on GitHub (here: > > https://github.com/php/php-src/issues/10414), but I have been advised > > that it's best to post here. > > > > What I would like to introduce/suggest, is the ability to create a > > closure from a method using the first-class-callable syntax (eg: > > MyClass::aMethod(...)), for a non-static method, statically. > > > > Currently, the following code causes an error. > > > > ``` > > class Test { > > public function test(): string { return 'test'; } > > } > > > > $closure = Test::test(...); > > ``` > > > > I understand why the error is thrown, but, and I'm unsure of the > > specifics regarding this, I think we could delay the error until the > > closure was called. The reason for this, is that closures can be bound, > > so if you followed on from the code above, you could do the following: > > > > ``` > > $closure->bindTo(new Test); > > $closure(); > > ``` > > > > The above would bind the closure in $closure to the scope of an object, > > which in this case, is the class that the method belongs to. > > > > The best example I can think, for this, would be when filter a > > collection of instances. If you were using a collection library, you > > would currently have something like the following: > > > > ``` > > $collection->filter(function (Str $string) { > > return !$string->empty(); > > }); > > ``` > > > > Whereas it would be much nicer to have the following: > > > > ``` > > $collection->filter(Str::empty(...)); > > ``` > > > > In this situation, the collection library would be responsible for > > binding the closure to the value it is iterating. > > So you'd implement this yourself elsewhere? > > class Str { > public function empty(): bool { ... } > } > > I don't see in this example how this is any better than what is already currently possible: > > class Str { > public static function empty(Str $s): bool { ... } > } > > $collection->filter(Str::empty(...)); > > > I have limited experience with PHPs source, and C in general, but my > > understanding would be that if we were creating a closure, we would > > skip the check for the static method. The code responsible for handling > > the closure call would most require some additional functionality to > > check if it was bound to a valid instance, returning an error if it > > isn't, and then returning an error if it isn't bound at all and the > > method isn't static. > > > > The more I think about it, the more I think this may require a new type > > of Closure, or at least a runtime applied interface, to help developers > > determine whether a closure was created using first-class-callable > > syntax. > > This is, I think, the important part here, and would be a prerequisite. Right now there's no way (as far as I know) to differentiate a closure that is callable from one that would be callable if it were bound to an object. That's generally not a huge deal in practice as unbound closures are not often used, but what you're suggesting would make them much more likely. Also, a static closure cannot be bound, so you cannot just blindly bind whatever callable you're passed to $this, in your example. (Besides, blindly binding a closure to $this sounds like a great security hole.) > > So for some variant of this to work, I think you'd first need to think through how to (easily and without dipping into reflection) determine if a closure object is bindable (static or not) and if it's already bound. Once that's figured out, then we can see what, if any, short-hand way to make a not-yet-bound closure makes sense. (Which could be FCC syntax or not, I don't know.) > > --Larry Garfield > > -- > PHP Internals - PHP Runtime Development Mailing List > To unsubscribe, visit: https://www.php.net/unsub.php > >
--- Best Regards, *Ollie Read*

Robert Landers

3 years ago
On Mon, Jan 23, 2023 at 4:39 PM Ollie Read <php@ollie.codes> wrote:
> > You are absolutely correct. I guess the solution would be to handle it differently in this case. > > Creating a closure from a static method would be fine, as it creates a static closure, but when attempting to create a static closure from a non-static method, it would instead return a closure that errors if it isn't bound to an appropriate object. You'd most likely want to restrict this to public methods only, which would help with the security issues. > > There's already a check there that throws an error, so we can already tell the difference there, but the tricky part will be in the returned closure. Perhaps something like "BindingClosure" that throws the static error when attempting to call it unbound, or better yet, a more descriptive error about it requiring binding. > > Would that be feasible? > > On Sun, Jan 22, 2023, at 8:36 PM, Larry Garfield wrote: > > On Sun, Jan 22, 2023, at 11:45 AM, Ollie Read wrote: > > > Hello all, > > > > > > I've created a feature request issue on GitHub (here: > > > https://github.com/php/php-src/issues/10414), but I have been advised > > > that it's best to post here. > > > > > > What I would like to introduce/suggest, is the ability to create a > > > closure from a method using the first-class-callable syntax (eg: > > > MyClass::aMethod(...)), for a non-static method, statically. > > > > > > Currently, the following code causes an error. > > > > > > ``` > > > class Test { > > > public function test(): string { return 'test'; } > > > } > > > > > > $closure = Test::test(...); > > > ``` > > > > > > I understand why the error is thrown, but, and I'm unsure of the > > > specifics regarding this, I think we could delay the error until the > > > closure was called. The reason for this, is that closures can be bound, > > > so if you followed on from the code above, you could do the following: > > > > > > ``` > > > $closure->bindTo(new Test); > > > $closure(); > > > ``` > > > > > > The above would bind the closure in $closure to the scope of an object, > > > which in this case, is the class that the method belongs to. > > > > > > The best example I can think, for this, would be when filter a > > > collection of instances. If you were using a collection library, you > > > would currently have something like the following: > > > > > > ``` > > > $collection->filter(function (Str $string) { > > > return !$string->empty(); > > > }); > > > ``` > > > > > > Whereas it would be much nicer to have the following: > > > > > > ``` > > > $collection->filter(Str::empty(...)); > > > ``` > > > > > > In this situation, the collection library would be responsible for > > > binding the closure to the value it is iterating. > > > > So you'd implement this yourself elsewhere? > > > > class Str { > > public function empty(): bool { ... } > > } > > > > I don't see in this example how this is any better than what is already currently possible: > > > > class Str { > > public static function empty(Str $s): bool { ... } > > } > > > > $collection->filter(Str::empty(...)); > > > > > I have limited experience with PHPs source, and C in general, but my > > > understanding would be that if we were creating a closure, we would > > > skip the check for the static method. The code responsible for handling > > > the closure call would most require some additional functionality to > > > check if it was bound to a valid instance, returning an error if it > > > isn't, and then returning an error if it isn't bound at all and the > > > method isn't static. > > > > > > The more I think about it, the more I think this may require a new type > > > of Closure, or at least a runtime applied interface, to help developers > > > determine whether a closure was created using first-class-callable > > > syntax. > > > > This is, I think, the important part here, and would be a prerequisite. Right now there's no way (as far as I know) to differentiate a closure that is callable from one that would be callable if it were bound to an object. That's generally not a huge deal in practice as unbound closures are not often used, but what you're suggesting would make them much more likely. Also, a static closure cannot be bound, so you cannot just blindly bind whatever callable you're passed to $this, in your example. (Besides, blindly binding a closure to $this sounds like a great security hole.) > > > > So for some variant of this to work, I think you'd first need to think through how to (easily and without dipping into reflection) determine if a closure object is bindable (static or not) and if it's already bound. Once that's figured out, then we can see what, if any, short-hand way to make a not-yet-bound closure makes sense. (Which could be FCC syntax or not, I don't know.) > > > > --Larry Garfield > > > > -- > > PHP Internals - PHP Runtime Development Mailing List > > To unsubscribe, visit: https://www.php.net/unsub.php > > > > > > --- > Best Regards, > *Ollie Read*
FWIW, I think we can "throw out" any automatic binding, that just complicates things. In my mind, Test::Func(...) should be treated the same as ['Test', 'Func'] or 'Test::Func' until it is called and if some fancy framework wants to do something special with the closure, it can do so. FWIW, I didn't even know this syntax checked anything until today. I can think of a number of cases where delaying the error to actual execution is beneficial. 1. Just because the method/object doesn't exist, doesn't mean it won't exist by the time it is called -- this is PHP after all. 2. I can have a file with 100 million of these things without triggering any autoloading. 3. PHPStorm currently doesn't trigger this syntax as an error.

Ollie Read

3 years ago
Oh, I didn't mean to suggest that it automatically binds. My second suggestion for how to achieve this does require some sort of automation. If you create a closure from Str::someMethod($arg1, $arg2) where someMethod isn't static, it should create a closure with the signature fn(Str $object, $arg1, $arg2), which would wrap a call to [$object, 'someMethod]($arg1, $arg2). However, perhaps the simplest solution, and one that could be done realtively easily and quickly, would be to delay the error, and allow static closures to bound with Closure::bind() if they represent a non-static method. --- Best Regards, *Ollie Read*

Deleu

3 years ago
On Mon, Jan 23, 2023, 1:16 PM Ollie Read <php@ollie.codes> wrote:
> Oh, I didn't mean to suggest that it automatically binds. > > My second suggestion for how to achieve this does require some sort of > automation. If you create a closure from Str::someMethod($arg1, $arg2) > where someMethod isn't static, it should create a closure with the > signature fn(Str $object, $arg1, $arg2), which would wrap a call to > [$object, 'someMethod]($arg1, $arg2).
I think this starts to wonders in the realm of Partial Function Application https://wiki.php.net/rfc/partial_function_application

Ollie Read

3 years ago
There's definitely similarity, but I would say it sits somewhere between the two. Rather than reference a partial method call, or create a closure for a method, you're delaying a method call. Or rather, referencing a method. We have the ::class pseudo property, so I see this like an equivalent, but for methods. On Mon, Jan 23, 2023, at 4:50 PM, Deleu wrote:
> > > On Mon, Jan 23, 2023, 1:16 PM Ollie Read <php@ollie.codes> wrote: >> Oh, I didn't mean to suggest that it automatically binds. >> >> My second suggestion for how to achieve this does require some sort of automation. If you create a closure from Str::someMethod($arg1, $arg2) where someMethod isn't static, it should create a closure with the signature fn(Str $object, $arg1, $arg2), which would wrap a call to [$object, 'someMethod]($arg1, $arg2). > > I think this starts to wonders in the realm of Partial Function Application https://wiki.php.net/rfc/partial_function_application >
--- Best Regards, *Ollie Read*

Larry Garfield

3 years ago
On Mon, Jan 23, 2023, at 10:53 AM, Ollie Read wrote:
> There's definitely similarity, but I would say it sits somewhere > between the two. Rather than reference a partial method call, or create > a closure for a method, you're delaying a method call. Or rather, > referencing a method. We have the ::class pseudo property, so I see > this like an equivalent, but for methods.
So what you're really looking for is a shorter way to write this: foo(fn(Str $s) => $s->beep()); Now you're definitely in the realm of PFA, pipes, and that kind of functionality. And, sadly, Internals has shown a great deal of resistance to that kind of functionality. I have a user-space implementation of pipes that includes utilities to do something similar[1], but most functional languages I know also don't have a really strong native way to "turn around" a call like that. I'd love a solution for that, but so far it has proven both elusive and controversial. --Larry Garfield [1] https://github.com/Crell/fp/blob/master/src/object.php#L28

Robert Landers

3 years ago
On Mon, Jan 23, 2023 at 6:31 PM Larry Garfield <larry@garfieldtech.com> wrote:
> > On Mon, Jan 23, 2023, at 10:53 AM, Ollie Read wrote: > > There's definitely similarity, but I would say it sits somewhere > > between the two. Rather than reference a partial method call, or create > > a closure for a method, you're delaying a method call. Or rather, > > referencing a method. We have the ::class pseudo property, so I see > > this like an equivalent, but for methods. > > So what you're really looking for is a shorter way to write this: > > foo(fn(Str $s) => $s->beep()); > > Now you're definitely in the realm of PFA, pipes, and that kind of functionality. And, sadly, Internals has shown a great deal of resistance to that kind of functionality. I have a user-space implementation of pipes that includes utilities to do something similar[1], but most functional languages I know also don't have a really strong native way to "turn around" a call like that. > > I'd love a solution for that, but so far it has proven both elusive and controversial. > > --Larry Garfield > > [1] https://github.com/Crell/fp/blob/master/src/object.php#L28 > > -- > PHP Internals - PHP Runtime Development Mailing List > To unsubscribe, visit: https://www.php.net/unsub.php >
Speaking of partial application, it'd be amazing if it were available with the current closure syntax: $func = fn($x, $y, $z) => echo $x + $y + $z; $plus1 = $func(..., 1); $plus2n1 = $func(2, 1, ...); $a = $plus1(1, 2); $b = $plus2n1(5); I spent a few days last summer trying this out, but couldn't get the grammar parser to accept it. I think I have a local branch if someone would be willing to work on it with me. At least point me in the right direction. I don't have much free time these days, but it is certainly doable. Robert Landers Software Engineer Utrecht NL

Christoph Becker

3 years ago
On 23.01.2023 at 19:22, Robert Landers wrote:
> Speaking of partial application, it'd be amazing if it were available > with the current closure syntax: > > $func = fn($x, $y, $z) => echo $x + $y + $z; > $plus1 = $func(..., 1); > $plus2n1 = $func(2, 1, ...); > > $a = $plus1(1, 2); > $b = $plus2n1(5); > > I spent a few days last summer trying this out, but couldn't get the > grammar parser to accept it. I think I have a local branch if someone > would be willing to work on it with me. At least point me in the right > direction. I don't have much free time these days, but it is certainly > doable.
There has been an RFC for this[1] (including an implementation), which had been declined. [1] <https://wiki.php.net/rfc/partial_function_application>
-- Christoph M. Becker

Larry Garfield

3 years ago
On Mon, Jan 23, 2023, at 10:08 AM, Robert Landers wrote:
> In my mind, Test::Func(...) should be treated the same as ['Test', > 'Func'] or 'Test::Func' until it is called and if some fancy framework > wants to do something special with the closure, it can do so. FWIW, I > didn't even know this syntax checked anything until today. I can think > of a number of cases where delaying the error to actual execution is > beneficial. > > 1. Just because the method/object doesn't exist, doesn't mean it won't > exist by the time it is called -- this is PHP after all. > 2. I can have a file with 100 million of these things without > triggering any autoloading. > 3. PHPStorm currently doesn't trigger this syntax as an error.
This ship has long since sailed. Test::func(...) produces a \Callable object, using the Closure::fromCallable() mechanism internally. One important advantage (and why Nikita favored it over most other callable syntaxes) is that it respects scope visibility better than passing around a buncha strings. Changing that at this point is a major API break that is not going to happen. --Larry Garfield

Larry Garfield

3 years ago
On Mon, Jan 23, 2023, at 9:39 AM, Ollie Read wrote:
> You are absolutely correct. I guess the solution would be to handle it > differently in this case. > > Creating a closure from a static method would be fine, as it creates a > static closure, but when attempting to create a static closure from a > non-static method, it would instead return a closure that errors if it > isn't bound to an appropriate object. You'd most likely want to > restrict this to public methods only, which would help with the > security issues.
Then don't make it a non-static method? Using an object method as a static method has been bad practice for as long as I can remember and an error since sometime recently, I think, so let's not do that.
> There's already a check there that throws an error, so we can already > tell the difference there, but the tricky part will be in the returned > closure. Perhaps something like "BindingClosure" that throws the static > error when attempting to call it unbound, or better yet, a more > descriptive error about it requiring binding. > > Would that be feasible?
By the time the error is thrown is too late. It's a distinction that needs to be made before the bind. So you could do like: if ($fn->needsBinding()) { $fn = $fn->bindTo($o); } (Or something.) That said, it sounds like you're talking about something different anyway that is probably unfeasible. Also, please do not top post. --Larry Garfield

Dan Ackroyd

3 years ago
On Sun, 22 Jan 2023 at 17:45, Ollie Read <php@ollie.codes> wrote:
> > Hello all,
Hi Ollie,
> I've created a feature request issue on GitHub (here: https://github.com/php/php-src/issues/10414), but I have been advised that it's best to post here. > ... > I think we could delay the error until the closure was called.
That sounds like a complete non-starter. Adding something to the language that produces a closure that can't be called would be an instant new entry for PHPSadness.
> Whereas it would be much nicer to have the following: > > ``` > $collection->filter(Str::empty(...)); > ``` > > In this situation, the collection library would be responsible for binding the > closure to the value it is iterating.
How would the collection library know it was safe to bind the closure to each value it was iterating over? It sounds really type unsafe.
> What I would like to introduce/suggest, is the ability to create a closure > from a method using the first-class-callable syntax... > > The more I think about it, the more I think this may require a new ...
I think people should say clearly what the problem they are trying to solve first, before suggesting solutions. I think there's at least a couple of problems that can be thought about: i. Although the first-class-callable syntax allowed avoiding string based programming for most callables, it doesn't support referring to some things that you would want to call, including both instance methods, and constructors*. ii. Representing an instance method requires at least two pieces of info; which class it belongs to (which can be used to find the constructor) and the parameters of the method itself. That doesn't naturally fit into a closure. iii. The appropriate syntax for referencing a class instance method without it being string based isn't obvious. Or at least it isn't obvious to me. iv. Writing code that for callback methods is longer than it could be, e.g. as Larry Garfield wrote:
> So what you're really looking for is a shorter way to write this: > > foo(fn(Str $s) => $s->beep());
At the risk of suggesting an abomination, given a class of: class Zoq { public function __construct(private Fot $fot) {} public function Pik(string $zebranky): Frungy {...} } If it was possible to generate callable for the constructor with: $fnConstructor = Closure::fromClassConstructor(Zoq::class); // signature of $fnConstructor is the same as `function(Fot $fot): Zoq` Or for individual methods: $fnMethod = Closure::fromClassMethod(Zoq::class, 'Pik'); // signature of $fnMethod is the same as `function(Zoq $zoq, string $zebranky): Frungy` That sort of looks like a solution to most of the problems I think exist. For your particular problem, I believe that would allow: $fnMethod = Closure::fromClassMethod(Str::class, 'empty'); $collection->filter($fnMethod); Which isn't shorter, but at least allows passing the callables around with the type inspectable. Though it might be nicer if PHP had the ability to definte function signatures types. That sort of solution obviously doesn't address the problem of having to refer to the class method as a string, but as I said, I don't have a suggestion for that. Well, other than to use a different syntax.** cheers Dan Ack * constructors having their own terrible history. ** https://news-web.php.net/php.internals/114542

Larry Garfield

3 years ago
On Mon, Jan 23, 2023, at 12:32 PM, Dan Ackroyd wrote:
> On Sun, 22 Jan 2023 at 17:45, Ollie Read <php@ollie.codes> wrote: >> >> Hello all, > > Hi Ollie, > >> I've created a feature request issue on GitHub (here: https://github.com/php/php-src/issues/10414), but I have been advised that it's best to post here. >> ... >> I think we could delay the error until the closure was called. > > That sounds like a complete non-starter. Adding something to the > language that produces a closure that can't be called would be an > instant new entry for PHPSadness. > >> Whereas it would be much nicer to have the following: >> >> ``` >> $collection->filter(Str::empty(...)); >> ``` >> >> In this situation, the collection library would be responsible for binding the >> closure to the value it is iterating. > > How would the collection library know it was safe to bind the closure > to each value it was iterating over? It sounds really type unsafe. > > >> What I would like to introduce/suggest, is the ability to create a closure >> from a method using the first-class-callable syntax... >> >> The more I think about it, the more I think this may require a new ... > > I think people should say clearly what the problem they are trying to > solve first, before suggesting solutions. > > I think there's at least a couple of problems that can be thought about: > > i. Although the first-class-callable syntax allowed avoiding string > based programming for most callables, it doesn't support referring to > some things that you would want to call, including both instance > methods, and constructors*. > > ii. Representing an instance method requires at least two pieces of > info; which class it belongs to (which can be used to find the > constructor) and the parameters of the method itself. That doesn't > naturally fit into a closure. > > iii. The appropriate syntax for referencing a class instance method > without it being string based isn't obvious. Or at least it isn't > obvious to me. > > iv. Writing code that for callback methods is longer than it could be, > e.g. as Larry Garfield wrote: > >> So what you're really looking for is a shorter way to write this: >> >> foo(fn(Str $s) => $s->beep()); > > > At the risk of suggesting an abomination, given a class of: > > class Zoq > { > public function __construct(private Fot $fot) {} > > public function Pik(string $zebranky): Frungy {...} > } > > If it was possible to generate callable for the constructor with: > > $fnConstructor = Closure::fromClassConstructor(Zoq::class); > // signature of $fnConstructor is the same as `function(Fot $fot): Zoq` > > Or for individual methods: > > $fnMethod = Closure::fromClassMethod(Zoq::class, 'Pik'); > // signature of $fnMethod is the same as `function(Zoq $zoq, string > $zebranky): Frungy` > > That sort of looks like a solution to most of the problems I think exist. > > For your particular problem, I believe that would allow: > > $fnMethod = Closure::fromClassMethod(Str::class, 'empty'); > $collection->filter($fnMethod); > > Which isn't shorter, but at least allows passing the callables around > with the type inspectable. Though it might be nicer if PHP had the > ability to definte function signatures types. > > That sort of solution obviously doesn't address the problem of having > to refer to the class method as a string, but as I said, I don't have > a suggestion for that. Well, other than to use a different syntax.**
I don't think that actually helps. If you have the object already, then making an FCC from it already works: https://3v4l.org/GTAGR If you do not have the object already, then we run back into the issue I pointed out before where we need to differentiate a bindable from needs-binding callable, which is already an issue (though one I've not run into). I think that's a prerequisite for adding any dedicated syntax for "make a closure that doesn't work until it gets bound". --Larry Garfield

Dan Ackroyd

3 years ago
On Mon, 23 Jan 2023 at 20:51, Larry Garfield <larry@garfieldtech.com> wrote:
> > On Mon, Jan 23, 2023, at 12:32 PM, Dan Ackroyd wrote: > > > > > $fnConstructor = Closure::fromClassConstructor(Zoq::class); > > // signature of $fnConstructor is the same as `function(Fot $fot): Zoq` > > > > Or for individual methods: > > > > $fnMethod = Closure::fromClassMethod(Zoq::class, 'Pik'); > > // signature of $fnMethod is the same as `function(Zoq $zoq, string > > $zebranky): Frungy` > > I don't think that actually helps. If you have the object already,
Neither of new functions I was suggesting have the object already. Only the type.
> If you do not have the object already, then we run back into the issue > I pointed out before where we need to differentiate a bindable from > needs-binding callable,
That's not what I suggesting. Danack wrote:
> > $fnConstructor = Closure::fromClassConstructor(Zoq::class); > signature of $fnConstructor is the same as `function(Fot $fot): Zoq`
That doesn't need a binding at all. Danack wrote:
> > $fnMethod = Closure::fromClassMethod(Zoq::class, 'Pik'); > signature of $fnMethod is the same as `function(Zoq $zoq, string > $zebranky): Frungy`
First parameter is the object to be operated on. You wouldn't need to do any binding. *magic* would happen inside the function to take care of that.
> I think that's a prerequisite for adding any dedicated syntax > for "make a closure that doesn't work until it gets bound".
To be clear, I don't support anything like that. As I wrote: Danack wrote:
> > That sounds like a complete non-starter. Adding something to the > language that produces a closure that can't be called would be an > instant new entry for PHPSadness.
cheers Dan Ack

Larry Garfield

3 years ago
On Mon, Jan 23, 2023, at 3:52 PM, Dan Ackroyd wrote:
> On Mon, 23 Jan 2023 at 20:51, Larry Garfield <larry@garfieldtech.com> wrote: >> >> On Mon, Jan 23, 2023, at 12:32 PM, Dan Ackroyd wrote: >> >> > >> > $fnConstructor = Closure::fromClassConstructor(Zoq::class); >> > // signature of $fnConstructor is the same as `function(Fot $fot): Zoq` >> > >> > Or for individual methods: >> > >> > $fnMethod = Closure::fromClassMethod(Zoq::class, 'Pik'); >> > // signature of $fnMethod is the same as `function(Zoq $zoq, string >> > $zebranky): Frungy` >> >> I don't think that actually helps. If you have the object already, > > Neither of new functions I was suggesting have the object already. > Only the type. > >> If you do not have the object already, then we run back into the issue >> I pointed out before where we need to differentiate a bindable from >> needs-binding callable, > > That's not what I suggesting. > > Danack wrote: >> >> $fnConstructor = Closure::fromClassConstructor(Zoq::class); >> signature of $fnConstructor is the same as `function(Fot $fot): Zoq` > > That doesn't need a binding at all. > > Danack wrote: >> >> $fnMethod = Closure::fromClassMethod(Zoq::class, 'Pik'); >> signature of $fnMethod is the same as `function(Zoq $zoq, string >> $zebranky): Frungy` > > First parameter is the object to be operated on. You wouldn't need to > do any binding. *magic* would happen inside the function to take care > of that.
Oh, I see what you're getting at. I missed the signature on first read. So you're saying that would generate a callable equivalent to: $fnMethod = fn (Zok $zok, string $zebranky): Frungy => $zok->Pik($zebranky); That's an interesting idea, though probably not self-evident to many people. If we wanted to do that, I'd probably push for something more compact as a syntax, and a bit more auto-refactorable. I think the Ara language had syntax for something like this, $$->Pik(...) ? If I'm remembering correctly, which I may or may not be? (We're also now rather far afield from the OP's request, I think.) --Larry Garfield

Dan Ackroyd

3 years ago
On Mon, 23 Jan 2023 at 23:03, Larry Garfield <larry@garfieldtech.com> wrote:
> > So you're saying that would generate a callable equivalent to: > > $fnMethod = fn (Zok $zok, string $zebranky): Frungy => $zok->Pik($zebranky);
Yes.
> (We're also now rather far afield from the OP's request, I think.)
Well, yeah. Deliberately so. Starting a discussion with a solution prevents people discussing and agreeing on what the actual problems to be solved are, and fixates on a single, in this case 'not great', solution. As I said, I think people should say clearly what the problem they are trying to solve first, before suggesting solutions.
> I'd probably push for something more compact as a syntax
Quite. Indeed. Yes. Danack wrote:
> > Well, other than to use a different syntax. > > https://news-web.php.net/php.internals/114542
$(Zoq, Pik); cheers Dan Ack

Ollie Read

3 years ago
You're absolutely correct, the problem I was trying to solve was the ability to reference a method without throwing around magic strings, apologies if that wasn't clear. The whole idea, or rather, need/want for it came about because of time spent with Javas functional side, though I understand that's safer to do because of the way Java handles type safety. The ::class pseudo type is great for providing a classes FQN, and class-string is great for ensuring values are valid class strings when using static analysis, but we don't have anything for methods, outside of IDE completion. The solution wouldn't even need to generate a closure, TBH, it would generate something that could then be used in the same manner. Using the current features of PHP, I'd probably imagine something that does just about the same as this: $method = new ReflectionMethod(MyClass::class, 'method'); $collection->filter($method); Where the filter method does: foreach ($items as $item) { if ($method->invoke($item)) { // } } Although reflection doesn't have the overhead that everyone thinks it does, this is a less than ideal solution, but I think it accurately portrays what I'm looking for. --- Best Regards, *Ollie Read*

Robert Landers

3 years ago
On Tue, Jan 24, 2023 at 12:06 PM Ollie Read <php@ollie.codes> wrote:
> > You're absolutely correct, the problem I was trying to solve was the ability to reference a method without throwing around magic strings, apologies if that wasn't clear. > > The whole idea, or rather, need/want for it came about because of time spent with Javas functional side, though I understand that's safer to do because of the way Java handles type safety. > > The ::class pseudo type is great for providing a classes FQN, and class-string is great for ensuring values are valid class strings when using static analysis, but we don't have anything for methods, outside of IDE completion. > > The solution wouldn't even need to generate a closure, TBH, it would generate something that could then be used in the same manner. > > Using the current features of PHP, I'd probably imagine something that does just about the same as this: > > $method = new ReflectionMethod(MyClass::class, 'method'); > > $collection->filter($method); > > Where the filter method does: > > foreach ($items as $item) { > if ($method->invoke($item)) { > // > } > } > > Although reflection doesn't have the overhead that everyone thinks it does, this is a less than ideal solution, but I think it accurately portrays what I'm looking for. > > --- > Best Regards, > *Ollie Read*
> The ::class pseudo type is great for providing a classes FQN, and class-string is great for ensuring values are valid class strings when using static analysis, but we don't have anything for methods, outside of IDE completion. > > The solution wouldn't even need to generate a closure, TBH, it would generate something that could then be used in the same manner.
In C#, they have the `nameof()` operator that returns the name of the symbol passed. So you still end up with a string, but the symbol can easily be refactored. https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/operators/nameof Maybe something like this could help address the problem? Robert Landers Software Engineer Utrecht NL