Re: State of Generics and Collections

From: Date: Tue, 20 Aug 2024 12:42:19 +0000
Subject: Re: State of Generics and Collections
References: 1 2 3  Groups: php.internals 
Request: Send a blank email to [email protected] to get a copy of this message


On Tue, Aug 20, 2024, at 14:08, Arnaud Le Blanc wrote:
> Hi Rob,
> 
> On Mon, Aug 19, 2024 at 7:51 PM Rob Landers <[email protected]> wrote:
> 
> > > Invariance would make arrays very difficult to adopt, as a library can not start type
> > > hinting generic arrays without breaking user code, and users can not pass generic arrays to
> > > libraries until they start using generic arrays type declarations.
> >
> > This seems like a strawman argument, to a degree. In other words, it seems like you could
> > combine static arrays and fluid arrays to accomplish what you are seeking to do. In other words, use
> > static arrays but allow casting to treat it as "fluid."
> >
> > In other words, simply cast to get your example to compile:
> >
> > function f(array<int> $a) {}
> > function g(array $a) {}
> >
> > $a = (array<int>) [1]; // array unless cast
> >
> > f($a); // ok
> > g((array)$a); // ok
> >
> > And the other way:
> >
> > function f(array<int> $a) {}
> > function g(array $a) {}
> >
> > $a = [1];
> >
> > f((array<int>)$a); // ok, type check done during cast
> > g($a); // ok
> 
> There is potential for breaking changes in both of your examples:
> 
> If f() is a library function that used to be declared as `f(array
> $a), then changing its declaration to f(array<int> $a)` is a
> breaking change in the Static Arrays flavour, as it would break
> library users until they change their code to add casts.

I don't think we should be scared of breaking changes; php 9.0 is coming 🔜 anyway. You could
also consider it as "an array might be array<T>, but an array<T> is always an
array"

> 
> Similarly, the following code would break (when calling g()) if h()
> was changed to return an array<int>:
> 
> function h(): array {}
> function g(array $a);
> 
> $a = h();
> g($a);
> 
> Casting would allow users to pass generic arrays to libraries that
> don't support generics yet, but that's expensive as it requires a
> copy.

Why does it require a copy? It should only require a copy if the contents are changed (CoW) and at
that point, you can know what rules to apply based on the coerced/casted type. I'm doing a
similar thing for the Literal Strings RFC, where it is a type that is also indistinguishable from a
string until something happens to it and it is no longer a literal string.

So passing a array<int> to a function that only accepts an array shouldn't matter. Once
inside that function, all type-checking can be disabled for that array. One approach to that could
be to just smack a "type-check strategy" function pointer on zvals, potentially, as that
would give the most flexibility for casting, aliases, generics, etc. Don't get me started on
the current type checking; it is a mess and inconsistent depending on what is doing the checking
(constructor promoted props, properties, method args, function args). Then you can just copy the
zval, change a function pointer, but point it to the same array (which will CoW) and change the
strategy during casting.

In other words, you could cheaply cast an array<int> to array<string> by (essentially)
changing a couple of function pointers, but array<string> to array<int> would be
expensive. So I imagine there would strategies for changing strategies... probably. I don't
know, I literally just thought of this off the top of my head, so it probably needs more work.

> 
> Best Regards,
> Arnaud
> 


— Rob


Thread (36 messages)

« previous php.internals (#125068) next »