Re: [Discussion] Callable types via Interfaces
| From: | Levi Morrison via internals | Date: | Thu, 20 Apr 2023 23:23:13 +0000 |
| Subject: | Re: [Discussion] Callable types via Interfaces | ||
| References: | 1 | Groups: | php.internals |
| Request: | Send a blank email to internals+get-120093@lists.php.net to get a copy of this message | ||
On Thu, Apr 20, 2023 at 11:25 AM Larry Garfield <larry@garfieldtech..com> wrote:
>
> Hi folks. This is a pre-discussion, in a sense, before a formal RFC. Nicolas Grekas and I
> have been kicking around some ideas for how to address the desire for typed callables, and have
> several overlapping concepts to consider. Before going down the rabbit hole on any of them we want
> to gauge the general feeling about the approaches to see what is worth pursuing.
>
> We have three "brain dump" RFCs on this topic, although these are all still in
> super-duper early stages so don't sweat the details in them at this point. We just want to
> discuss the basic concepts, which I have laid out below.
>
>
> https://wiki.php.net/rfc/allow_casting_closures_into_single-method_interface_implementations
>
> https://wiki.php.net/rfc/allow-closures-to-declare-interfaces-they-implement
> https://wiki.php.net/rfc/structural-typing-for-closures
>
> ## The problem
>
> function takeTwo(callable $c): int
> {
> return $c(1, 2);
> }
>
> Right now, we have no way to statically enforce that $c is a callable that takes 2 ints and
> returns an int. We can document it, but that's it.
>
> There is one loophole, in that an interface may require an __invoke() method:
>
> interface TwoInts
> {
> public function __invoke(int $a, int $b): int;
> }
>
> And then a class may implement TwoInts, and takeTwo() can type against TwoInts. However, that
> works only for classes, which are naturally considerably more verbose than a simple closure and
> represent only a subset of the possible callable types.
>
> The usual discussion has involved a way to specify a callable type's signature, like so:
>
> function takeTwo(callable(int $a, int $b): int $c)
> {
> return $c(1, 2);
> }
>
> But that runs quickly into the problem of verbosity, reusability, and type aliases, and the
> discussion usually dies there.
>
I'm going to stop here. Two big things:
1. I think reducing verbosity can be left for the future. We don't
have to solve that right now.
2. I think there's another more important reason previous attempts
failed: they will inevitably burden the programmer without type
inference.For the moment, let's assume this signature:
function takeTwo(callable(int $x, int $y): int $c);
What happens if I pass a short-closure?
takeTwo(fn ($x, $y) => $x + $y);
I would be annoyed if I had to write the type info, but particularly
the return type.
Today, if I just used a static analysis tool, there's no problem:
/** @param callable(int $x, int $y): int $c */
function takeTwo(callable $c);
takeTwo(fn ($x, $y) => $x + $y);
3. And another reason they failed: callables are going to want
generic types pretty commonly. Think array_filter, array_map, etc.
So, I think these brain dump RFCs are all focusing on the wrong problems.