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 <rob@bottled.codes> 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

Reply via email to