Re: [Discussion] Revisiting typed closures
| From: | Larry Garfield | Date: | Thu, 02 Jul 2026 19:14:32 +0000 |
| Subject: | Re: [Discussion] Revisiting typed closures | ||
| References: | 1 2 3 4 | Groups: | php.internals |
| Request: | Send a blank email to internals+get-131722@lists.php.net to get a copy of this message | ||
On Wed, Jul 1, 2026, at 5:02 PM, Rowan Tommins [IMSoP] wrote:
> On 1 July 2026 20:02:27 BST, Seifeddine Gmati <azjezz@carthage.software> wrote:
>>Making
Closure generic (e.g., say ¸²ðÍ�Zä²Ä¡ ècU
>>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.
>
> FWIW, C# has a long list of overloads for expressing generic lambda
> types, like Action<T1,T2>, Action<T1,T2,T3>, Func<T1,T2,TResult> and so
> on and on and on
>
> <https://github.com/dotnet/dotnet/blob/b0f34d51fccc69fd334253924abd8d6853fad7aa/src/runtime/src/libraries/System.Private.CoreLib/src/System/Action.cs>
>
>
> <https://github.com/dotnet/dotnet/blob/b0f34d51fccc69fd334253924abd8d6853fad7aa/src/runtime/src/libraries/System.Private.CoreLib/src/System/Function.cs>
>
> Ugly, and unlikely to even work in a PHP implementation of generics.
>
> On the other hand, it has an elegant "delegate" syntax for what are
> effectively named callable types:
>
> https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/delegates/
>
> That *would* translate well to PHP. Translating one of their examples:
>
> delegate ProcessBookCallback(Book $book): void;
>
> public function processPaperbackBooks(ProcessBookCallback $processBook): void {
> foreach ($this->list as $b) {
> if ($b->paperback) {
> $processBook($b);
> }
> }
> }
>
> Just an additional angle to throw into the mix.
>
>
> Rowan Tommins
> [IMSoP]
I am very much in favor of callable types, in concept. (Which should surprise no one.)
I don't have very strong feelings on the spelling at the moment yet; I suspect this is a case
where the parser will dictate to us what is possible, and that will greatly limit our options or
just make the decision for us. (That's how we ended up with fn() for short-closures. It was
the shortest thing the parser would let us get away with.)
On the question of callable-vs-closure, I agree that today, between FCC and PFA a Closure is
absolutely trivial to produce, so we don't need to support the variety of legacy callable
formats. The one caveat to that is for compiled code; you cannot store a closure in a serialized
form or in generated code; functions and static methods are easy enough to store that way (as a
string and array, respectively), ugly as those formats are. Methods, anon functions, etc. however
are much harder, and there's no globally standard way of cheating there. I suspect the best we
can do without scope creeping ourselves to death is just support closures and leave it to
implementers to turn other callable formats into a closure, which isn't that hard these days.
Generics syntax is the wrong format to use for this, full stop. Let's not even go down that
pathway. Generics are orthogonal.
The main semantic question is whether callable/closure types should be defineable inline, or only as
a reference (as in Rowan's C# example above.) In the past, there's been non-small
pushback to defining them inline, as that can make for very hard to read function declarations and
if the same signature is used in multiple places, you have to manually keep them in sync.
However, we don't have type aliases (yet), which would resolve that issue. And when proposals
have been floated to allow separate definition of a function interface (as Nicolas and I proposed),
it's been rejected as too complicated. So we're really at an impasse on this.
My own stance is that we should just define them inline for now, and if that leads to messy function
signatures then that's just all the more impetus for us to get off our butts and decide on a
way to do type aliases. :-) That's better than having a one-off syntax for function signature
definition but something entirely different for complex union/intersection types, or restricted
types (like "positive int"), etc. Let's have all types inline-able, and then a
globally-defined alias/reuse mechanism that supports all of them consistently.
The one should not block the other, especially not given how PHP Internals works.
--Larry Garfield