Re: [RFC] "arrayable" pseudo type hint

This is only part of a thread. view whole thread
  108366
February 4, 2020 12:48 norbert@aimeos.com (Aimeos | Norbert Sendetzky)
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:
> 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 >
  108397
February 4, 2020 17:18 larry@garfieldtech.com ("Larry Garfield")
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
  108398
February 4, 2020 18:05 norbert@aimeos.com (Aimeos | Norbert Sendetzky)
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 ); }
  108399
February 4, 2020 18:17 rowan.collins@gmail.com (Rowan Tommins)
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]
  108400
February 4, 2020 18:40 norbert@aimeos.com (Aimeos | Norbert Sendetzky)
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
  108402
February 4, 2020 21:18 larry@garfieldtech.com ("Larry Garfield")
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
  108403
February 4, 2020 22:24 andreas@dqxtech.net (Andreas Hennings)
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
> > --Larry Garfield > > -- > PHP Internals - PHP Runtime Development Mailing List > To unsubscribe, visit: http://www.php.net/unsub.php > >
  108411
February 6, 2020 11:59 nikita.ppv@gmail.com (Nikita Popov)
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
  108401
February 4, 2020 18:51 guilliam.xavier@gmail.com (Guilliam Xavier)
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