[RFC] "arrayable" pseudo type hint

php.internals

Aimeos | Norbert Sendetzky

6 years ago
Since PHP 7.1, there's the "iterable" pseudo type hint that matches "array" or "Traversable". PHP frameworks would profit from support of an "arrayable" pseudo type hint that matches "array" and all objects that can be used like arrays. For that, "arrayable" objects have to implement these interfaces: - ArrayAccess - Countable - Traversable (i.e. either Iterator or IteratorAggregate) Implementing "arrayable" pseudo type, we could pass arrays or all objects that can be used like arrays to methods and do: function useArrayable( arrayable $arg ) : arrayable { $cnt = count( $arg ); $value = $arg['key']; foreach( $arg as $key => $entry ); return $arg; } Best use cases are: - Laravel Collection (https://github.com/laravel/framework/blob/6.x/src/Illuminate/Support/Collection.php) - Aimeos Map (https://github.com/aimeos/map) No new interface is proposed because we can check if objects implement: ArrayAccess && Countable && Traversable Because "array" !== "arrayable", "arrayable objects will not be accepted by array_* functions and all functions that only use "array" as type hint for parameters and return types. This proposal is not obsolete by the implementation of union types (i.e. array|Traversable) because union data types are types/interfaces combined by OR. "arrayable" is a combination of OR and AND: array|ArrayAccess&Countable&Traversable Also, "arrayable" won't supersede "iterable" because - "iterable" matches arrays, iterators and generators - "arrayable" matches arrays and objects that can be used like arrays "Can be used like arrays" doesn't include support for empty() because it works for empty arrays but not for "arrayable" objects without elements. If possible and requested, this may be addressed by another RFC. As Larry Garfield suggested, this pre-RFC proposes concentrates on the "arrayable" pseudo type only. It does NOT discusses that: - arrayable objects can be converted to arrays (Steven Wade works on an RFC for a __toArray() magic function) - ArrayObject or any other arrayable object makes the array_* functions less relevant in the future - arrayable objects can be passed to array_* functions in the future

Aimeos | Norbert Sendetzky

6 years ago
> Since PHP 7.1, there's the "iterable" pseudo type hint that matches > "array" or "Traversable". > > PHP frameworks would profit from support of an "arrayable" pseudo type > hint that matches "array" and all objects that can be used like arrays. > > For that, "arrayable" objects have to implement these interfaces: > - ArrayAccess > - Countable > - Traversable (i.e. either Iterator or IteratorAggregate) > > > Implementing "arrayable" pseudo type, we could pass arrays or all > objects that can be used like arrays to methods and do: > > function useArrayable( arrayable $arg ) : arrayable { > $cnt = count( $arg ); > $value = $arg['key']; > foreach( $arg as $key => $entry ); > return $arg; > }
Implementation is pretty straight forward: https://github.com/aimeos/php-src/commit/8c4f7f1142e20696b9481f4329928a6140eb05fd It also contains is_arrayable() and ReflectionClass::isArrayable() methods. Tests haven't been implemented yet.

=?utf-8?B?5rGf5rmW5aSn6Jm+5LuB?=

6 years ago
> This proposal is not obsolete by the implementation of union types (i.e. > array|Traversable) because union data types are types/interfaces > combined by OR. "arrayable" is a combination of OR and AND: > > array|ArrayAccess&Countable&Traversable
True. But it's also easy to implement in userland. Just create a new interface that extends these three interfaces, then union types can work. ```php interface Arrayable extends ArrayAccess, Countable, Traversable {} function array_x (array|Arrayable $arr) {} ``` This is not something that userland cannot implement or very difficult to implement. IMO it will make php more complex if we include this in the core. Actually what you suggested has been totally covered by the future scopes of the union types RFC. If you really want to explore this idea, I think they are worth more attention, instead of this specific case. Regards, CHU Zhaowei

Andreas Hennings

6 years ago
On Sun, 24 Nov 2019 at 16:55, CHU Zhaowei <me@jhdxr.com> wrote:
> > This proposal is not obsolete by the implementation of union types (i.e. > > array|Traversable) because union data types are types/interfaces > > combined by OR. "arrayable" is a combination of OR and AND: > > > > array|ArrayAccess&Countable&Traversable > > True. But it's also easy to implement in userland. Just create a new > interface that extends these three interfaces, then union types can work. > > ```php > interface Arrayable extends ArrayAccess, Countable, Traversable {} > > function array_x (array|Arrayable $arr) {} > ``` > > This is not something that userland cannot implement or very difficult to > implement. IMO it will make php more complex if we include this in the > core. Actually what you suggested has been totally covered by the future > scopes of the union types RFC.
or "intersection types": https://wiki.php.net/rfc/intersection_types

Aimeos | Norbert Sendetzky

6 years ago
I would like to modify my initial concept of an "arrayable" type because PHP core developers seems to be in favor of the upcoming union data types instead of adding a new "arrayable" pseudo type similar to "iterable". So, I would like to propose an "Arrayable" interface that combines ArrayAccess, Countable and Traversable interfaces and adds a toArray() method: interface Arrayable extends ArrayAccess, Countable, Traversable { public function toArray() : array; } Then, methods signatures can support array and Array-like objects: function useArrayable( array|Arrayable $arg ) : array|Arrayable { $cnt = count( $arg ); $value = $arg['key']; foreach( $arg as $key => $entry ); is_object( $arg ) { $nativeArray = $arg->toArray(); } return $arg; } Adding a toArray() method also avoids controversy around using a magic __toArray() method, even if this would be easier for developers in this case. If union data types are available and "iterable" is implemented as alias, an alias "arrayable" for "array|Arrayable" may be added as well. Am 19.11.19 um 23:21 schrieb Aimeos | Norbert Sendetzky:

Larry Garfield

6 years ago
On Tue, Feb 4, 2020, at 6:48 AM, Aimeos | Norbert Sendetzky wrote:
> I would like to modify my initial concept of an "arrayable" type because > PHP core developers seems to be in favor of the upcoming union data > types instead of adding a new "arrayable" pseudo type similar to "iterable". > > So, I would like to propose an "Arrayable" interface that combines > ArrayAccess, Countable and Traversable interfaces and adds a toArray() > method: > > interface Arrayable extends ArrayAccess, Countable, Traversable > { > public function toArray() : array; > } > > Then, methods signatures can support array and Array-like objects: > > function useArrayable( array|Arrayable $arg ) : array|Arrayable { > $cnt = count( $arg ); > $value = $arg['key']; > foreach( $arg as $key => $entry ); > is_object( $arg ) { $nativeArray = $arg->toArray(); } > return $arg; > } > > Adding a toArray() method also avoids controversy around using a magic > __toArray() method, even if this would be easier for developers in this > case. > > If union data types are available and "iterable" is implemented as > alias, an alias "arrayable" for "array|Arrayable" may be added as well.
The more I think on it, the less I like `arrayable`. PHP arrays are a terrible data structure from a type system point of view, with too much functionality crammed into one variable type. If you want an object that shoves all the various bits of arrayness into one object, there's already ArrayObject, which you can extend if desired. Especially with union types, amalgam built in types like this are even less needed. Aside from iterable, the only other built-in alias I would see is one for array|ArrayAccess. But... it's now possible to do exactly like that, so I don't know how useful it would be. --Larry Garfield

Aimeos | Norbert Sendetzky

6 years ago
Am 04.02.20 um 18:18 schrieb Larry Garfield:
>> interface Arrayable extends ArrayAccess, Countable, Traversable >> { >> public function toArray() : array; >> } >> >> Then, methods signatures can support array and Array-like objects: >> >> function useArrayable( array|Arrayable $arg ) : array|Arrayable { >> $cnt = count( $arg ); >> $value = $arg['key']; >> foreach( $arg as $key => $entry ); >> is_object( $arg ) { $nativeArray = $arg->toArray(); } >> return $arg; >> } >> >> If union data types are available and "iterable" is implemented as >> alias, an alias "arrayable" for "array|Arrayable" may be added as >> well. > > The more I think on it, the less I like `arrayable`. PHP arrays are > a terrible data structure from a type system point of view, with too > much functionality crammed into one variable type.
The array internals are outside the scope of this proposal and are not affected in any way by this proposal.
> If you want an object that shoves all the various bits of arrayness > into one object, there's already ArrayObject, which you can extend if > desired.
Tried that already, but other core developers made very clear that ArrayObject is one of the worst implementations in PHP and that it's not going to be improved or touched in any way and using it is highly discouraged.
> Especially with union types, amalgam built in types like this are > even less needed.
If an "arrayable" pseudo type should be implemented is subject to discussion. It would be a short form for "array|Arrayable" (native type or interface for array like objects) only offering a syntactical sugar for developers.
> Aside from iterable, the only other built-in alias I would see is one > for array|ArrayAccess. But... it's now possible to do exactly like > that, so I don't know how useful it would be.
"array|ArrayAccess" is not sufficient because arrays are also countable and traversable and this isn't possible with union data types: interface Arrayable extends ArrayAccess, Countable, Traversable function useArrayable( array|Arrayable $arg ) { $cnt = count( $arg ); $value = $arg['key']; foreach( $arg as $key => $entry ); }

Rowan Collins

6 years ago
On Tue, 4 Feb 2020 at 18:06, Aimeos | Norbert Sendetzky <norbert@aimeos.com> wrote:
> Am 04.02.20 um 18:18 schrieb Larry Garfield: > > The more I think on it, the less I like `arrayable`. PHP arrays are > > a terrible data structure from a type system point of view, with too > > much functionality crammed into one variable type. > > The array internals are outside the scope of this proposal and are not > affected in any way by this proposal. >
I think Larry's point was that the flexibility of PHP's array type makes it really hard to pin down whether a given object is "array-like" or not, and which attributes a particular function actually cares about. A general "intersection type" system might be more useful, because then you could require the parts you specifically needed, such as "traversable&ArrayAccess" or "traversable&countable". Regards,
-- Rowan Tommins [IMSoP]

Aimeos | Norbert Sendetzky

6 years ago
Am 04.02.20 um 19:17 schrieb Rowan Tommins:
> I think Larry's point was that the flexibility of PHP's array type makes it > really hard to pin down whether a given object is "array-like" or not, and > which attributes a particular function actually cares about.
What else besides array access, counting and traversing is possible that may differ from classes that implement those interfaces?
> A general "intersection type" system might be more useful, because then you > could require the parts you specifically needed, such as > "traversable&ArrayAccess" or "traversable&countable".
I think that's too complicated and we should make it as easy as possible for PHP developers. Also, there's already an RFC for intersection types but it was never adopted: https://wiki.php.net/rfc/intersection_types

Larry Garfield

6 years ago
On Tue, Feb 4, 2020, at 12:40 PM, Aimeos | Norbert Sendetzky wrote:
> Am 04.02.20 um 19:17 schrieb Rowan Tommins: > > I think Larry's point was that the flexibility of PHP's array type makes it > > really hard to pin down whether a given object is "array-like" or not, and > > which attributes a particular function actually cares about. > > What else besides array access, counting and traversing is possible that > may differ from classes that implement those interfaces? > > > A general "intersection type" system might be more useful, because then you > > could require the parts you specifically needed, such as > > "traversable&ArrayAccess" or "traversable&countable". > > I think that's too complicated and we should make it as easy as possible > for PHP developers. > > Also, there's already an RFC for intersection types but it was never > adopted: https://wiki.php.net/rfc/intersection_types
Rowan is exactly right and said it better than I did. The point is that "I can count() it", "I can foreach() it" and "I can bracket it" are three different things; in practice, a given function likely only cares about one, maybe two of those at a time. Adding a type for "an object that mimics all of the dumb things arrays do, but now passes differently" doesn't strike me as useful; it strikes me as the sort of thing I'd reject in a code review if someone tried to do it in user space. The problem with PHP arrays is that they're not arrays; they're a hash map with poor safety, lame error semantics, and some cheats to make them kinda sorta look like arrays if you don't look too carefully. In practice, they create more bugs than they fix. Intersection types would be absolutely delightful and I want them for numerous reasons. --Larry Garfield

Andreas Hennings

6 years ago
On Tue, 4 Feb 2020 at 22:19, Larry Garfield <larry@garfieldtech.com> wrote:
> On Tue, Feb 4, 2020, at 12:40 PM, Aimeos | Norbert Sendetzky wrote: > > Am 04.02.20 um 19:17 schrieb Rowan Tommins: > > > I think Larry's point was that the flexibility of PHP's array type > makes it > > > really hard to pin down whether a given object is "array-like" or not, > and > > > which attributes a particular function actually cares about. > > > > What else besides array access, counting and traversing is possible that > > may differ from classes that implement those interfaces? > > > > > A general "intersection type" system might be more useful, because > then you > > > could require the parts you specifically needed, such as > > > "traversable&ArrayAccess" or "traversable&countable". > > > > I think that's too complicated and we should make it as easy as possible > > for PHP developers. > > > > Also, there's already an RFC for intersection types but it was never > > adopted: https://wiki.php.net/rfc/intersection_types > > > Rowan is exactly right and said it better than I did. > > The point is that "I can count() it", "I can foreach() it" and "I can > bracket it" are three different things; in practice, a given function > likely only cares about one, maybe two of those at a time. Adding a type > for "an object that mimics all of the dumb things arrays do, but now passes > differently" doesn't strike me as useful; it strikes me as the sort of > thing I'd reject in a code review if someone tried to do it in user space. > > The problem with PHP arrays is that they're not arrays; they're a hash map > with poor safety, lame error semantics, and some cheats to make them kinda > sorta look like arrays if you don't look too carefully. In practice, they > create more bugs than they fix. >
There is one good thing about arrays: They are passed along by-value by default, which gives them similar qualities as an "immutable" object. If you pass an array to a function as a parameter which is not by-reference, you can expect the original array to remain unchanged. (Objects or referenced variables within that array can still be modified of course) A function parameter which allows an object OR an array loses this advantage. If I have the choice between \stdClass and array for "unstructured tree of data", I will always prefer the array. ~~ Andreas

Nikita Popov

6 years ago
On Tue, Feb 4, 2020 at 7:40 PM Aimeos | Norbert Sendetzky < norbert@aimeos.com> wrote:
> Am 04.02.20 um 19:17 schrieb Rowan Tommins: > > I think Larry's point was that the flexibility of PHP's array type makes > it > > really hard to pin down whether a given object is "array-like" or not, > and > > which attributes a particular function actually cares about. > > What else besides array access, counting and traversing is possible that > may differ from classes that implement those interfaces? > > > A general "intersection type" system might be more useful, because then > you > > could require the parts you specifically needed, such as > > "traversable&ArrayAccess" or "traversable&countable". > > I think that's too complicated and we should make it as easy as possible > for PHP developers. > > Also, there's already an RFC for intersection types but it was never > adopted: https://wiki.php.net/rfc/intersection_types >
FWIW, that's an old RFC that predates the current implementation of union types in PHP 8 by a few years... A new proposal for intersection types should stand a better chance now, especially as it can mostly reuse the (relatively involved) technical underpinnings for the union types implementation. Nikita

Guilliam Xavier

6 years ago
On Tue, Feb 4, 2020 at 1:48 PM Aimeos | Norbert Sendetzky <norbert@aimeos.com> wrote:
> > [...] > > interface Arrayable extends ArrayAccess, Countable, Traversable > { > public function toArray() : array; > } > > [...] > > If union data types are available and "iterable" is implemented as > alias, an alias "arrayable" for "array|Arrayable" may be added as well.
About that last point: it would not be possible to have both the `Arrayable` interface and the `arrayable` reserved word because PHP is case-insensitive for those things (the same way you cannot declare an `interface Iterable` because of the `iterable` reserved word, <https://3v4l.org/Fhqar>), so you would need another name (if you want the alias)
-- Guilliam Xavier