Hello internals,
I would like to revisit the idea of giving closures a typed call signature.
e.g. Closure(int, string): array -- enforced at the point a value crosses a
type boundary (an argument, a return, a property), the same places any other
type is checked.
Today a closure can only be typed as Closure or callable, neither of which
says anything about its parameters or return, even though that information is
right there.
I know this is not new ground: Callable Prototypes was declined in 2016, and
Garfield and Grekas shared two further RFCs in 2023 -- Structural Typing for
Closures, and Allow Closures to Declare Interfaces they Implement -- both still
in draft.
Before taking it further, I would like to know whether closure typing
is still considered worth pursuing -- or whether the topic is now regarded as
settled.
References:
- https://wiki.php.net/rfc/callable-types
- https://wiki.php.net/rfc/structural-typing-for-closures
- https://wiki.php.net/rfc/allow-closures-to-declare-interfaces-they-implement
Thanks.
There was also this: https://wiki.php.net/rfc/functional-interfaces
A re-reading of the very brief thread, shows Dmitry thought it an inelegant
use/abuse of the type system, and more generally there was a feeling that
anon classes could be used in their place.
Cheers
Joe
Hello internals,
I would like to revisit the idea of giving closures a typed call signature.
e.g. Closure(int, string): array -- enforced at the point a value crosses a
type boundary (an argument, a return, a property), the same places any
other
type is checked.Today a closure can only be typed as Closure or callable, neither of which
says anything about its parameters or return, even though that information
is
right there.I know this is not new ground: Callable Prototypes was declined in 2016,
and
Garfield and Grekas shared two further RFCs in 2023 -- Structural Typing
for
Closures, and Allow Closures to Declare Interfaces they Implement -- both
still
in draft.Before taking it further, I would like to know whether closure typing
is still considered worth pursuing -- or whether the topic is now regarded
as
settled.References:
https://wiki.php.net/rfc/allow-closures-to-declare-interfaces-they-implement
Thanks.
Hello internals,
I would like to revisit the idea of giving closures a typed call signature.
e.g. Closure(int, string): array -- enforced at the point a value crosses a
type boundary (an argument, a return, a property), the same places any other
type is checked.Today a closure can only be typed as Closure or callable, neither of which
says anything about its parameters or return, even though that information is
right there.I know this is not new ground: Callable Prototypes was declined in 2016, and
Garfield and Grekas shared two further RFCs in 2023 -- Structural Typing for
Closures, and Allow Closures to Declare Interfaces they Implement -- both still
in draft.Before taking it further, I would like to know whether closure typing
is still considered worth pursuing -- or whether the topic is now regarded as
settled.References:
- https://wiki.php.net/rfc/callable-types
- https://wiki.php.net/rfc/structural-typing-for-closures
- https://wiki.php.net/rfc/allow-closures-to-declare-interfaces-they-implement
Thanks.
I would rather wait for generics and possibly make Closure a generic type.
— Rob
I would rather wait for generics and possibly make Closure a generic type.
Fair point, but I am worried that's misleading, and I would keep the two
separate.
Generics are a much larger and more contentious topic; typed closure is a much
smaller topic: a runtime shape check on a Closure value at the boundary, nothing
more.
The concrete benefit is typing the callback a higher-order method takes,
so a mismatched map/filter callback is caught at the call site rather than deep
inside the operation (or only by static analysis):
// expected signature: (int) => bool
public function filter(Closure(int): bool $pred): static;
// ok
$nums->filter(fn(int $n): bool => $n % 2 === 0);
// TypeError: string not expected
$nums->filter(fn(string $s): bool => $s !== '');
Typed closures neither need generics nor blocks them. In fact, eventually typed
closures will support generics. But I would rather keep these topics separate.
On its own, is a runtime-checked closure signature something you would find
useful?
I would rather wait for generics and possibly make Closure a generic
type.Fair point, but I am worried that's misleading, and I would keep the two
separate.Generics are a much larger and more contentious topic; typed closure is a
much
smaller topic: a runtime shape check on a Closure value at the boundary,
nothing
more.The concrete benefit is typing the callback a higher-order method takes,
so a mismatched map/filter callback is caught at the call site rather than
deep
inside the operation (or only by static analysis):// expected signature: (int) => bool public function filter(Closure(int): bool $pred): static; // ok $nums->filter(fn(int $n): bool => $n % 2 === 0); // TypeError: string not expected $nums->filter(fn(string $s): bool => $s !== '');Typed closures neither need generics nor blocks them. In fact, eventually
typed
closures will support generics. But I would rather keep these topics
separate.On its own, is a runtime-checked closure signature something you would find
useful?
I would find this very useful. That said, restricting it to closures and
not any callable is very limiting, particularly now that we have
first-class callable syntax.
Perhaps something more along the line of :
callable(int):bool
This would give more flexibility, and provide us contract guarantees. Right
now, SA can validate these, but only via annotations.
--
Matthew Weier O'Phinney
mweierophinney@gmail.com
https://mwop.net/
he/him
I would rather wait for generics and possibly make Closure a generic type.
— Rob
Hi Rob,
I don't think generics help here. PHP doesn't have generics at all
yet, and realistically they're at least a couple of years away. But
even once they land, they wouldn't solve this.
Making Closure generic (e.g., say Closure<Input, Output> ) would
require Input to be variadic to stand in for a closure's parameter
list, and variadic generics are a different beast entirely.
They've never appeared in any proposal, they don't really make sense
for a number of reasons, and if they were ever added at all, it would
be years after generics themselves. So "wait for generics" here
effectively means waiting indefinitely. And arity aside, a generic
Closure<...> still couldn't express which arguments are required
versus optional, nor type a closure that is itself generic. A
dedicated syntax like fn<T>(T, string=): T handles all of it
cleanly:
- The generic parameter (
<T>) - Te required and optional arguments (
=marker ) - The return type ( following
:)
Cheers,
Seifeddine
Hello internals,
I would like to revisit the idea of giving closures a typed call signature.
e.g. Closure(int, string): array -- enforced at the point a value crosses a
type boundary (an argument, a return, a property), the same places any other
type is checked.Today a closure can only be typed as Closure or callable, neither of which
says anything about its parameters or return, even though that information is
right there.I know this is not new ground: Callable Prototypes was declined in 2016, and
Garfield and Grekas shared two further RFCs in 2023 -- Structural Typing for
Closures, and Allow Closures to Declare Interfaces they Implement -- both still
in draft.Before taking it further, I would like to know whether closure typing
is still considered worth pursuing -- or whether the topic is now regarded as
settled.References:
- https://wiki.php.net/rfc/callable-types
- https://wiki.php.net/rfc/structural-typing-for-closures
- https://wiki.php.net/rfc/allow-closures-to-declare-interfaces-they-implement
Thanks.
Hi Matheus,
I'm in favor of adding this. However, syntax wise, i would rather we
re-use the fn keyword, e.g:
fn(int): stringfn(string, int=): string(=indicates the parameter is optional)fn(...string): string(...indicates the parameter is variadic)fn(&string): void(&indicates the parameter is by-reference)
I also think restricting this to allow only Closure instance inputs
is the right approach ( given that we have FCC ), as i think type
checking array callables, and strings would be annoying and probably
wasteful.
Cheers,
Seifeddine.