[RFC][DISCUSSION] Collection Inspection

php.internals

Michael Morris

8 years ago
Ok, here's another idea I've been mulling over. This I know is possible because I've done it using user land code, specifically Drupal 8's Assertion\Inspector class. https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Component%21Assertion%21Inspector.php/class/Inspector/8.5.x These methods provide a means to inspect collections - arrays usually but also Traversables. They fill a hole in the PHP library - the ability to check collection integrity. To answer the question "Why in core?" - well, Drupal needing to do this using a library referenced only by assert() statements creates an undocumented dependency for any class that wants to run these checks. From a pragmatic standpoint it just feels wrong. Most other languages have some means of doing these checks. IMPLEMENTATION STYLE #1: collectionof operator The style of implementation I like the most is a collectionof operator in parallel to the instance_of operator. It would be instanceof's plural brother, so $arg collectionof \SomeClass To pass the check $arg must be of the iterable psuedotype with all its values being a \SomeClass object. This is all well and good, but collection integrity checks are usually going to be looking to see if all the members are the same scalar. $arg collectionof string For language consistency we would need to allow instance_of to do the same, which it currently does not. $arg instanceof string This does create duplication with the is_* family of functions, but instanceof already overlaps is_a heavily. IMPLEMENTATION STYLE #2: all_* This style takes the is_* family and creates plural cousins for them. Example: all_string($arg); This is most similar to the user land solution. Following the current is_* family this gives us all_array(), all_bool(), all_callable(), all_double(), all_float(), all_int(), all_integer(), all_iterable(), all_long(), all_null(), all_numeric(), all_object(), all_real(), all_scalar(), and all_string(). One other function, all_are(), would be necessary for class and interface inspection. That's a lot of new functions, and the chance of BC breaking with someone's code is very high, so I don't personally like this approach. I'm putting it out there as alternate because despite that shortcoming it is a sensible alternative.

Derick Rethans

8 years ago
On Wed, 24 Jan 2018, Michael Morris wrote:
> Ok, here's another idea I've been mulling over. This I know is possible > because I've done it using user land code, specifically Drupal 8's > Assertion\Inspector class. > > https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Component%21Assertion%21Inspector.php/class/Inspector/8.5.x > > These methods provide a means to inspect collections - arrays usually but > also Traversables. They fill a hole in the PHP library - the ability to > check collection integrity.
IMO, it makes a lot more sense to check integrity when creating the "array" structure. Instead, I would suggest to add a native Collection type, that takes a "type" as argument. They aren't quite full generics, but it does 1. fix something; 2. isn't really complicated. What I am suggesting is to add a new syntax "Collection<$type>", mimicking a class, but having a type as "argument": $a = new Collection<integer>; And then $a can act as if you'd use an ArrayAccess'ed class. Upon each set or update, the type of the value can then be checked against the type. Consequently, this would also mean you can type hint on Collection<type> instead of for example an earlier suggested array of type, where upon passing in the array each member was checked for its type (slow). And on top of this, this could be extended to do proper generics too. cheers, Derick
-- https://derickrethans.nl | https://xdebug.org | https://dram.io Like Xdebug? Consider a donation: https://xdebug.org/donate.php, or become my Patron: https://www.patreon.com/derickr twitter: @derickr and @xdebug

Ryan Pallas

8 years ago
On Thu, Jan 25, 2018 at 7:52 AM, Derick Rethans <derick@php.net> wrote:
> On Wed, 24 Jan 2018, Michael Morris wrote: > > > Ok, here's another idea I've been mulling over. This I know is possible > > because I've done it using user land code, specifically Drupal 8's > > Assertion\Inspector class. > > > > https://api.drupal.org/api/drupal/core%21lib%21Drupal% > 21Component%21Assertion%21Inspector.php/class/Inspector/8.5.x > > > > These methods provide a means to inspect collections - arrays usually but > > also Traversables. They fill a hole in the PHP library - the ability to > > check collection integrity. > > IMO, it makes a lot more sense to check integrity when creating the > "array" structure. Instead, I would suggest to add a native Collection > type, that takes a "type" as argument. They aren't quite full generics, > but it does 1. fix something; 2. isn't really complicated. > > What I am suggesting is to add a new syntax "Collection<$type>", > mimicking a class, but having a type as "argument": > >
Just like to point out if it's considered a class, or takes the same space there is likely collisions. While most collection classes on github look to be in a namespace, there are lots that aren't. Of course github only gives us a view of open source projects that happen to be on github.

Derick Rethans

8 years ago
On Thu, 25 Jan 2018, Ryan Pallas wrote:
> On Thu, Jan 25, 2018 at 7:52 AM, Derick Rethans <derick@php.net> wrote: > > > On Wed, 24 Jan 2018, Michael Morris wrote: > > > > > Ok, here's another idea I've been mulling over. This I know is possible > > > because I've done it using user land code, specifically Drupal 8's > > > Assertion\Inspector class. > > > > > > https://api.drupal.org/api/drupal/core%21lib%21Drupal% > > 21Component%21Assertion%21Inspector.php/class/Inspector/8.5.x > > > > > > These methods provide a means to inspect collections - arrays usually but > > > also Traversables. They fill a hole in the PHP library - the ability to > > > check collection integrity. > > > > IMO, it makes a lot more sense to check integrity when creating the > > "array" structure. Instead, I would suggest to add a native Collection > > type, that takes a "type" as argument. They aren't quite full generics, > > but it does 1. fix something; 2. isn't really complicated. > > > > What I am suggesting is to add a new syntax "Collection<$type>", > > mimicking a class, but having a type as "argument": > > > > > Just like to point out if it's considered a class, or takes the same space > there is likely collisions. While most collection classes on github look to > be in a namespace, there are lots that aren't. Of course github only gives > us a view of open source projects that happen to be on github.
PHP owns the top-level namespace. It has always done that. It's even documented: http://docs.php.net/manual/en/userlandnaming.rules.php cheers, Derick
-- https://derickrethans.nl | https://xdebug.org | https://dram.io Like Xdebug? Consider a donation: https://xdebug.org/donate.php, or become my Patron: https://www.patreon.com/derickr twitter: @derickr and @xdebug

Michael Morris

8 years ago
On Thu, Jan 25, 2018 at 10:21 AM, Derick Rethans <derick@php.net> wrote:
> > PHP owns the top-level namespace. It has always done that. It's even > documented: http://docs.php.net/manual/en/userlandnaming.rules.php > >
That doesn't stop the bellyaching when the refactoring becomes necessary. If possible, it is best to avoid choosing a name that will cause a lot of projects to refactor.

Ryan Pallas

8 years ago
On Thu, Jan 25, 2018 at 9:21 AM, Derick Rethans <derick@php.net> wrote:
> On Thu, 25 Jan 2018, Ryan Pallas wrote: > > > On Thu, Jan 25, 2018 at 7:52 AM, Derick Rethans <derick@php.net> wrote: > > > > > On Wed, 24 Jan 2018, Michael Morris wrote: > > > > > > > Ok, here's another idea I've been mulling over. This I know is > possible > > > > because I've done it using user land code, specifically Drupal 8's > > > > Assertion\Inspector class. > > > > > > > > https://api.drupal.org/api/drupal/core%21lib%21Drupal% > > > 21Component%21Assertion%21Inspector.php/class/Inspector/8.5.x > > > > > > > > These methods provide a means to inspect collections - arrays > usually but > > > > also Traversables. They fill a hole in the PHP library - the ability > to > > > > check collection integrity. > > > > > > IMO, it makes a lot more sense to check integrity when creating the > > > "array" structure. Instead, I would suggest to add a native Collection > > > type, that takes a "type" as argument. They aren't quite full generics, > > > but it does 1. fix something; 2. isn't really complicated. > > > > > > What I am suggesting is to add a new syntax "Collection<$type>", > > > mimicking a class, but having a type as "argument": > > > > > > > > Just like to point out if it's considered a class, or takes the same > space > > there is likely collisions. While most collection classes on github look > to > > be in a namespace, there are lots that aren't. Of course github only > gives > > us a view of open source projects that happen to be on github. > > PHP owns the top-level namespace. It has always done that. It's even > documented: http://docs.php.net/manual/en/userlandnaming.rules.php
And yet, it's a point of contention that every proposal adding to the root namespace has faced.

Bishop Bettini

8 years ago
On Thu, Jan 25, 2018 at 9:52 AM, Derick Rethans <derick@php.net> wrote:
> On Wed, 24 Jan 2018, Michael Morris wrote: > > > Ok, here's another idea I've been mulling over. This I know is possible > > because I've done it using user land code, specifically Drupal 8's > > Assertion\Inspector class. > > > > https://api.drupal.org/api/drupal/core%21lib%21Drupal%21Comp > onent%21Assertion%21Inspector.php/class/Inspector/8.5.x > > > > These methods provide a means to inspect collections - arrays usually but > > also Traversables. They fill a hole in the PHP library - the ability to > > check collection integrity. > > IMO, it makes a lot more sense to check integrity when creating the > "array" structure. Instead, I would suggest to add a native Collection > type, that takes a "type" as argument. They aren't quite full generics, > but it does 1. fix something; 2. isn't really complicated. > > What I am suggesting is to add a new syntax "Collection<$type>", > mimicking a class, but having a type as "argument": > > $a = new Collection<integer>; > > And then $a can act as if you'd use an ArrayAccess'ed class. > Upon each set or update, the type of the value can then be checked > against the type. >
Agreed, and we can get *almost* there today with: $collection = collection_of('is_int', [ 1, 2 ]); $collection[] = M_PI; // exception thrown given: function collection_of(callable $validator, $input = []) { $collection = new class($input) extends \ArrayObject { public function offsetSet($index, $value) { if (($this->validator)($value)) return parent::offsetSet($index, $value); throw new \DomainException; } }; $collection->validator = $validator; foreach ($input as $key => $value) $collection[$key] = $value; return $collection; }
> Consequently, this would also mean you can type hint on Collection<type> > instead of for example an earlier suggested array of type, where upon > passing in the array each member was checked for its type (slow). >
Regrettably, class_alias cannot quite work here, because the validator is passed at run-time, so making this part of the language seems a good place to go. Cheers, bishop

Michael Morris

8 years ago
On Thu, Jan 25, 2018 at 10:14 AM, Bishop Bettini <bishop@php.net> wrote:
> > Agreed, and we can get *almost* there today with: > > $collection = collection_of('is_int', [ 1, 2 ]); >
Ok, let's stay on topic please. This RFC discussion is about an operator or family of functions to verify that a given $var is a collection. Objects which enforce collection integrity is a tangential but whole other topic outside the scope of what I proposed in the first post.

Rowan Collins

8 years ago
On 25/01/2018 18:56, Michael Morris wrote:
> Ok, let's stay on topic please. > This RFC discussion is about an operator or family of functions to verify > that a given $var is a collection. Objects which enforce collection > integrity is a tangential but whole other topic outside the scope of what I > proposed in the first post.
Well, it's more convergent than tangential: if Derick's "generics-lite" were implemented, the desire for a collectionof operator might be lower, because people would "$foo instanceof Collection<Bar>" instead. That said, the two don't cover all the same use cases, but it's useful to ask the hypothetical question "if we already had generics, would we still be keen to add the collectionof operator?" Regards,
-- Rowan Collins [IMSoP]

Michael Morris

8 years ago
On Thu, Jan 25, 2018 at 2:16 PM, Rowan Collins <rowan.collins@gmail.com> wrote:
> On 25/01/2018 18:56, Michael Morris wrote: > >> Ok, let's stay on topic please. >> This RFC discussion is about an operator or family of functions to verify >> that a given $var is a collection. Objects which enforce collection >> integrity is a tangential but whole other topic outside the scope of what >> I >> proposed in the first post. >> > > > Well, it's more convergent than tangential: if Derick's "generics-lite" > were implemented, the desire for a collectionof operator might be lower, > because people would "$foo instanceof Collection<Bar>" instead. >
Yes, but given what has been said on the issue by the senior maintainers, the chances for Derrick's "generics lite" counter-proposal ever seeing the light of day are somewhere between 0, null, false and empty string.

Rowan Collins

8 years ago
On 25/01/2018 14:52, Derick Rethans wrote:
> IMO, it makes a lot more sense to check integrity when creating the > "array" structure. Instead, I would suggest to add a native Collection > type, that takes a "type" as argument. They aren't quite full generics, > but it does 1. fix something; 2. isn't really complicated. > > What I am suggesting is to add a new syntax "Collection<$type>", > mimicking a class, but having a type as "argument": > > $a = new Collection<integer>;
So would I be right in thinking this would mean adding basic support for generics to the engine, but not letting new ones be defined in userland? So, a bit like how internal or extension classes have the ability to do more overloading of casts and operators than userland classes? If so, I like the concept, but wonder whether the parts of the implementation it would need are the easy parts of the hard ones. I guess not being able to inherit from the generic would get rid of a bunch of complexity, but don't really know where else the challenge lies. Regards,
-- Rowan Collins [IMSoP]

Michael Morris

8 years ago
On Thu, Jan 25, 2018 at 2:10 PM, Rowan Collins <rowan.collins@gmail.com> wrote:
> On 25/01/2018 14:52, Derick Rethans wrote: > >> IMO, it makes a lot more sense to check integrity when creating the >> "array" structure. Instead, I would suggest to add a native Collection >> type, that takes a "type" as argument. They aren't quite full generics, >> but it does 1. fix something; 2. isn't really complicated. >> >> What I am suggesting is to add a new syntax "Collection<$type>", >> mimicking a class, but having a type as "argument": >> >> $a = new Collection<integer>; >> > > So would I be right in thinking this would mean adding basic support for > generics to the engine, but not letting new ones be defined in userland? > So, a bit like how internal or extension classes have the ability to do > more overloading of casts and operators than userland classes? > > If so, I like the concept, but wonder whether the parts of the > implementation it would need are the easy parts of the hard ones. I guess > not being able to inherit from the generic would get rid of a bunch of > complexity, but don't really know where else the challenge lies. >
He's getting the syntax from Java. Java collections allow you to specify the type they are constrained to in <> after the class name. Type Script does something similar. The problem is that Java already has the policing mechanics present in the runtime to police insertions into the collection and guarantee they are of the correct type. PHP has no such mechanism in place and in previous discussion threads the primary maintainers have underscored that adding such is highly difficult. It is also completely tangential to and a distraction from what I propose and would like to focus on here. Regardless of whether PHP ever gets a core class or construct for type uniform collections, the ability to verify that variables are a "collection of something" will remain. That is the issue I want to discuss and address here - verifying that existing variables are a given collection type, not debating pie-in-the-sky, may never be implementable classes that self insure their own integrity. So, given `$a collectionof string` the operator returns whether or not $a is, at that time, a collection of strings (be it an array or other iterable). It doesn't insure $a will stay that way - it's just a check of the variables status at a given moment, which is the best that can be hoped for in PHP.

Niklas Keller

8 years ago
Michael Morris <tendoaki@gmail.com> schrieb am Do., 25. Jan. 2018, 21:19:
> On Thu, Jan 25, 2018 at 2:10 PM, Rowan Collins <rowan.collins@gmail.com> > wrote: > > > On 25/01/2018 14:52, Derick Rethans wrote: > > > >> IMO, it makes a lot more sense to check integrity when creating the > >> "array" structure. Instead, I would suggest to add a native Collection > >> type, that takes a "type" as argument. They aren't quite full generics, > >> but it does 1. fix something; 2. isn't really complicated. > >> > >> What I am suggesting is to add a new syntax "Collection<$type>", > >> mimicking a class, but having a type as "argument": > >> > >> $a = new Collection<integer>; > >> > > > > So would I be right in thinking this would mean adding basic support for > > generics to the engine, but not letting new ones be defined in userland? > > So, a bit like how internal or extension classes have the ability to do > > more overloading of casts and operators than userland classes? > > > > If so, I like the concept, but wonder whether the parts of the > > implementation it would need are the easy parts of the hard ones. I guess > > not being able to inherit from the generic would get rid of a bunch of > > complexity, but don't really know where else the challenge lies. > > > > He's getting the syntax from Java. Java collections allow you to specify > the type they are constrained to in <> after the class name. Type Script > does something similar. The problem is that Java already has the policing > mechanics present in the runtime to police insertions into the collection > and guarantee they are of the correct type. PHP has no such mechanism in > place and in previous discussion threads the primary maintainers have > underscored that adding such is highly difficult. > > It is also completely tangential to and a distraction from what I propose > and would like to focus on here. Regardless of whether PHP ever gets a > core class or construct for type uniform collections, the ability to verify > that variables are a "collection of something" will remain. That is the > issue I want to discuss and address here - verifying that existing > variables are a given collection type, not debating pie-in-the-sky, may > never be implementable classes that self insure their own integrity. > > So, given `$a collectionof string` the operator returns whether or not $a > is, at that time, a collection of strings (be it an array or other > iterable). It doesn't insure $a will stay that way - it's just a check of > the variables status at a given moment, which is the best that can be hoped > for in PHP. >
That should be a function, not an operator, unless you can bring up very good arguments to make it an operator. Regards, Niklas

Michael Morris

8 years ago
On Thu, Jan 25, 2018 at 2:39 PM, Niklas Keller <me@kelunik.com> wrote:
> > So, given `$a collectionof string` the operator returns whether or not $a >> is, at that time, a collection of strings (be it an array or other >> iterable). It doesn't insure $a will stay that way - it's just a check of >> the variables status at a given moment, which is the best that can be >> hoped >> for in PHP. >> > > That should be a function, not an operator, unless you can bring up very > good arguments to make it an operator. >
Consistency with instanceof. Hmm, it might be possible to just pile this all onto instanceof - following Derrick's mention of how Java does things, so here's a third implementation approach. It would look like this: $a instanceof array<string> Returns true if $a is an array (or implements array access) and that all it's members are strings. $b instanceof SomeClass<string> Returns true if SomeClass can be iterated and contains only strings. The question is can the token parser handle that pattern? Or does the current usage of < and > in their boolean form block this? If this third approach is accepted then we have no BC breaks at all since there's no new keyword or function. We just have a *very* expanded instanceof operator.

Niklas Keller

8 years ago
> > On Thu, Jan 25, 2018 at 2:39 PM, Niklas Keller <me@kelunik.com> wrote: > >> >> So, given `$a collectionof string` the operator returns whether or not $a >>> is, at that time, a collection of strings (be it an array or other >>> iterable). It doesn't insure $a will stay that way - it's just a check of >>> the variables status at a given moment, which is the best that can be >>> hoped >>> for in PHP. >>> >> >> That should be a function, not an operator, unless you can bring up very >> good arguments to make it an operator. >> > > Consistency with instanceof. > > > > Hmm, it might be possible to just pile this all onto instanceof - > following Derrick's mention of how Java does things, so here's a third > implementation approach. It would look like this: > > $a instanceof array<string> >
That might work, but array<string> should only return true if it's an array, not for anything that implements ArrayAccess.
> Returns true if $a is an array (or implements array access) and that all > it's members are strings. > > $b instanceof SomeClass<string> > > Returns true if SomeClass can be iterated and contains only strings. >
This would block generics with that syntax then.
> The question is can the token parser handle that pattern? Or does the > current usage of < and > in their boolean form block this? > > If this third approach is accepted then we have no BC breaks at all since > there's no new keyword or function. We just have a *very* expanded > instanceof operator. >
Regards, Niklas

Michael Morris

8 years ago
On Thu, Jan 25, 2018 at 3:04 PM, Niklas Keller <me@kelunik.com> wrote:
> >> >> $a instanceof array<string> >> > > That might work, but array<string> should only return true if it's an > array, not for anything that implements ArrayAccess. > >
Related: On Thu, Jan 25, 2018 at 4:11 PM, Levi Morrison <levim@php.net> wrote:
> > > I see no value to this operator being applied to non-array > traversables.
If an array access object can't masquerade as an array it loses some of its value, but Niklas is right - it is important to distinguish such objects from native arrays. One solution would be to promote "iterable" to keyword status. The flexibility to take any iterable will be needed I think. $a instanceof iterable<string> Would return true for anything iterable (which we can already test with is_iterable() ) where all values where strings. On Thu, Jan 25, 2018 at 4:11 PM, Levi Morrison <levim@php.net> wrote:
> > Our iterators cannot always be reliably rewound, such as > when using generators. Checking that the generator returns only > strings would consume all the input and would therefore be useless.
True - I hadn't thought of those. But as of PHP 7 generators can type declare their return value. So, given `$a instanceof iterable<string>`, if $a is a reference to a generator, then the engine could check the return type declaration and only give true on a match without attempting to use the generator. We can follow this pattern farther - The return of an ArrayAccess::offsetGet and Iterator::current() can be similarly tested by instanceof rather than actually pulling data from these methods. We are having the return rely on the promise of the code, but in each case TypeError would be raised anyway if it breaks it's promise to instanceof so errors aren't being avoided.
> Returns true if $a is an array (or implements array access) and that all >> it's members are strings. >> >> $b instanceof SomeClass<string> >> >> Returns true if SomeClass can be iterated and contains only strings. >> > > This would block generics with that syntax then. >
I don't understand this comment. On Thu, Jan 25, 2018 at 5:28 PM, Larry Garfield <larry@garfieldtech.com> wrote:
> > > Is this to ensure that everything coming OUT of a collection is a given > type, > or that everything put IN a collection is a given type? >
Ensure (or try to ensure, since reporting the promises of another method's return type isn't certain) that things coming OUT are a given type. Leave the headache of guarding inputs for another day and RFC.
> > Asserting that "at this point in time, everything in this collection is a > given type" is honestly fairly useless unless it's enforced to stay that > way.
No more useless than type declarations are if a programmer does this... function foo (string $a) { $a = (int) $a; } Every feature of the language can be rendered useless by the right amount of stupidity. No feature recommendation should be beholden to the "what if a moron does X?" argument.

Niklas Keller

8 years ago
Michael Morris <tendoaki@gmail.com> schrieb am Fr., 26. Jan. 2018, 02:06:
> On Thu, Jan 25, 2018 at 3:04 PM, Niklas Keller <me@kelunik.com> wrote: > > > > >> > >> $a instanceof array<string> > >> > > > > That might work, but array<string> should only return true if it's an > > array, not for anything that implements ArrayAccess. > > > > > > Related: > > On Thu, Jan 25, 2018 at 4:11 PM, Levi Morrison <levim@php.net> wrote: > > > > > > > I see no value to this operator being applied to non-array > > traversables. > > > If an array access object can't masquerade as an array it loses some of its > value, but Niklas is right - it is important to distinguish such objects > from native arrays. One solution would be to promote "iterable" to keyword > status. The flexibility to take any iterable will be needed I think. > > $a instanceof iterable<string> > > Would return true for anything iterable (which we can already test with > is_iterable() ) where all values where strings. > > On Thu, Jan 25, 2018 at 4:11 PM, Levi Morrison <levim@php.net> wrote: > > > > Our iterators cannot always be reliably rewound, such as > > when using generators. Checking that the generator returns only > > strings would consume all the input and would therefore be useless. > > > True - I hadn't thought of those. But as of PHP 7 generators can type > declare their return value.
They can only define generator as return type, which is what they return when you call a generator function. Even if you could declare the return type of the generator, you'd still have the same problem with the yielded values. So, given `$a instanceof iterable<string>`, if
> $a is a reference to a generator, then the engine could check the return > type declaration and only give true on a match without attempting to use > the generator. > > We can follow this pattern farther - The return of an > ArrayAccess::offsetGet and Iterator::current() can be similarly tested by > instanceof rather than actually pulling data from these methods. > > We are having the return rely on the promise of the code, but in each case > TypeError would be raised anyway if it breaks it's promise to instanceof so > errors aren't being avoided. > > > > > Returns true if $a is an array (or implements array access) and that all > >> it's members are strings. > >> > >> $b instanceof SomeClass<string> > >> > >> Returns true if SomeClass can be iterated and contains only strings. > >> > > > > This would block generics with that syntax then. > > > > I don't understand this comment. >
You restrict these type parameters to iterators, but generics are useful in a lot more places.
> On Thu, Jan 25, 2018 at 5:28 PM, Larry Garfield <larry@garfieldtech.com> > wrote: > > > > > > > Is this to ensure that everything coming OUT of a collection is a given > > type, > > or that everything put IN a collection is a given type? > > > > Ensure (or try to ensure, since reporting the promises of another method's > return type isn't certain) that things coming OUT are a given type. Leave > the headache of guarding inputs for another day and RFC. > > > > > > Asserting that "at this point in time, everything in this collection is a > > given type" is honestly fairly useless unless it's enforced to stay that > > way. > > > No more useless than type declarations are if a programmer does this... > > function foo (string $a) { > $a = (int) $a; > } > > Every feature of the language can be rendered useless by the right amount > of stupidity. No feature recommendation should be beholden to the "what if > a moron does X?" argument. >
Regards, Niklas

Michael Morris

8 years ago
On Thu, Jan 25, 2018 at 11:59 PM, Niklas Keller <me@kelunik.com> wrote:
> Michael Morris <tendoaki@gmail.com> schrieb am Fr., 26. Jan. 2018, 02:06: > >> On Thu, Jan 25, 2018 at 3:04 PM, Niklas Keller <me@kelunik.com> wrote: >> >> > >> >> >> >> $a instanceof array<string> >> >> >> > >> > That might work, but array<string> should only return true if it's an >> > array, not for anything that implements ArrayAccess. >> > >> > >> >> Related: >> >> On Thu, Jan 25, 2018 at 4:11 PM, Levi Morrison <levim@php.net> wrote: >> >> > >> > >> > I see no value to this operator being applied to non-array >> > traversables. >> >> >> If an array access object can't masquerade as an array it loses some of >> its >> value, but Niklas is right - it is important to distinguish such objects >> from native arrays. One solution would be to promote "iterable" to >> keyword >> status. The flexibility to take any iterable will be needed I think. >> >> $a instanceof iterable<string> >> >> Would return true for anything iterable (which we can already test with >> is_iterable() ) where all values where strings. >> >> On Thu, Jan 25, 2018 at 4:11 PM, Levi Morrison <levim@php.net> wrote: >> > >> > Our iterators cannot always be reliably rewound, such as >> > when using generators. Checking that the generator returns only >> > strings would consume all the input and would therefore be useless. >> >> >> True - I hadn't thought of those. But as of PHP 7 generators can type >> declare their return value. > > > They can only define generator as return type, which is what they return > when you call a generator function. > > Even if you could declare the return type of the generator, you'd still > have the same problem with the yielded values. >
See my comment about coding around morons being a pointless exercise. You throw an error and force the moron to fix their code.
> > So, given `$a instanceof iterable<string>`, if >> $a is a reference to a generator, then the engine could check the return >> type declaration and only give true on a match without attempting to use >> the generator. >> >> We can follow this pattern farther - The return of an >> ArrayAccess::offsetGet and Iterator::current() can be similarly tested by >> instanceof rather than actually pulling data from these methods. >> >> We are having the return rely on the promise of the code, but in each case >> TypeError would be raised anyway if it breaks it's promise to instanceof >> so >> errors aren't being avoided. >> >> >> >> > Returns true if $a is an array (or implements array access) and that all >> >> it's members are strings. >> >> >> >> $b instanceof SomeClass<string> >> >> >> >> Returns true if SomeClass can be iterated and contains only strings. >> >> >> > >> > This would block generics with that syntax then. >> > >> >> I don't understand this comment. >> > > You restrict these type parameters to iterators, but generics are useful > in a lot more places. >
iterABLE --- not iterOR. Two different things. Iterable is a psuedotype in PHP covering anything that can be traversed with foreach, including std_class, which is what I think what you mean by generator. There's even an existing function, is_iterable() to detect them. `$a instanceof iterable` will return the same as `is_iterable($a);` Generics specifically are already detectable by `$a instanceof stdClass` `$a instanceof Iterator` detected that interface, but not arrays and generics. `$a instanceof iterable` would detect all three. The similarity of the names is regrettable, but it's already in place and can't be changed at this point.

Rowan Collins

8 years ago
On 26 January 2018 at 01:06, Michael Morris <tendoaki@gmail.com> wrote:
> > On Thu, Jan 25, 2018 at 4:11 PM, Levi Morrison <levim@php.net> wrote: > > > > Our iterators cannot always be reliably rewound, such as > > when using generators. Checking that the generator returns only > > strings would consume all the input and would therefore be useless. > > > True - I hadn't thought of those. But as of PHP 7 generators can type > declare their return value. So, given `$a instanceof iterable<string>`, if > $a is a reference to a generator, then the engine could check the return > type declaration and only give true on a match without attempting to use > the generator. > > We can follow this pattern farther - The return of an > ArrayAccess::offsetGet and Iterator::current() can be similarly tested by > instanceof rather than actually pulling data from these methods. > > We are having the return rely on the promise of the code, but in each case > TypeError would be raised anyway if it breaks it's promise to instanceof so > errors aren't being avoided. >
The more angles we approach this, the more it looks like generics, or at least the same basis. For instance, what you're describing here is that Iterator<string> would act like an extra interface that restricted the return type of current() to string. With full userland generics, that would actually be declarable like this: interface Iterator<T> extends Iterator { public function current(): T; public function next(): T; } Which would basically be a template so that Iterator<string> created an appropriately constrained interface, which you can actually create already: interface Iterator__string extends Iterator { public function current(): string; public function next(): string; } (You could actually use an auto-loader hack to do a lot of generics this way.) The main differences I can see between this and your suggestion are: - If it's an actual interface, the class's definition would need to explicitly list that it implements it. The wording you used implied that it might be more implicit, and automatically label the class as an "iterable<string>" if the signatures matched. - The iterable<string> syntax would be able to cover arrays as well as Iterators. We might decide that just as "iterable" stands for "Iterator or array", "iterable<string>" stands for "Iterator<string> or string[]". However, I think having "string[]" was previously rejected because of the cost of checking every element of the array, particular when the type is something slower to check, like "callable[]". I think this fits with where Derick was going earlier: we could have pseudo-generic interfaces like Iterator<string> internally without a full generics implementation. As long as the syntax and semantics were compatible, these could then be a stepping-stone to full generics being added later. Regards,
-- Rowan Collins [IMSoP]

Michael Morris

8 years ago
> The more angles we approach this, the more it looks like generics, or at > least the same basis.
Which is well outside the scope of what I'd like to see, so let's look at form 2 in closer detail - the all functions. bool all_array( mixed $var ) { if (is_iterable($var) && !is_callable) { foreach ($var as $v) { if (!is_array($v)) return false; } return true; } return false; } That is the base function signature and implementation. The is_callable() above prevents iteration over generators, potentially consuming them for no purpose. The other functions in the family and their single value equivalents are: is_bool -> all_bool is_callable -> all_callable is_double -> all_double is_float -> all_float is_int -> all_int is_iterable -> all_iterable is_long -> all_long is_null -> all_null is_numeric -> all_numeric is_object -> all_object is_real -> all_real is_resource -> all_resource is_scalar -> all_scalar is_string -> all_string is_subclass_of -> all_are On the last one - is_subclass_of is generally more useful than is_a, but will doing this cause confusion? Also, not all of these need to be included - the above is a list of candidate functions to create. Of all of these all_string() would probably see the most use followed by all_numeric(). This will give the tools to do the desired assertions in a clean, easy to read manner. Collections that must be of a single iterable class can also be verified: assert($a instanceof \SomeClass && all_string($a)); But most of the time $a will merely be an array.

Rowan Collins

8 years ago
On 25/01/2018 20:19, Michael Morris wrote:
> He's getting the syntax from Java.
Actually, from C++, and possibly from somewhere before that; at this point, it's pretty widely adopted.
> The problem is that Java already has the policing > mechanics present in the runtime to police insertions into the collection > and guarantee they are of the correct type. PHP has no such mechanism in > place and in previous discussion threads the primary maintainers have > underscored that adding such is highly difficult.
That's only partially true. As Bishop Bettini pointed out, you can *almost* get there with features already in the language, because you can implement the magic ArrayAccess interface and intercept all assignments using the $foo[]=$val and $foo[$bar]=$val syntaxes. The main problem, as usual, is references: you can't intercept "$ref =& $foo[$bar]; $ref=42;" - if you try, you get a somewhat cryptic notice that "Indirect modification of overloaded element of foo has no effect". Apart from that, this is a *lot* simpler as a problem than general type tracking, because we only care about those two specific operations, not everywhere that performs an assignment of any sort. In that sense, a full implementation of generics is actually simpler: the only actual type checks would be on parameters and return types, where they're already supported, e.g. class Stack<T> { public function push(T $item) { ... } public function pop(): T { ... } } Like I say, it might be perfectly reasonable to have both a collectionof operator and generics in the language, but I don't think it's unreasonable to think about where they overlap. Regards,
-- Rowan Collins [IMSoP]

Levi Morrison

8 years ago
> IMPLEMENTATION STYLE #1: collectionof operator > The style of implementation I like the most is a collectionof operator in > parallel to the instance_of operator. It would be instanceof's plural > brother, so > > $arg collectionof \SomeClass > > To pass the check $arg must be of the iterable psuedotype with all its > values being a \SomeClass object. > > This is all well and good, but collection integrity checks are usually > going to be looking to see if all the members are the same scalar. > > $arg collectionof string > > For language consistency we would need to allow instance_of to do the same, > which it currently does not. > > $arg instanceof string > > This does create duplication with the is_* family of functions, but > instanceof already overlaps is_a heavily.
I see no value to this operator being applied to non-array traversables. Our iterators cannot always be reliably rewound, such as when using generators. Checking that the generator returns only strings would consume all the input and would therefore be useless.

Larry Garfield

8 years ago
On Thursday, January 25, 2018 4:11:02 PM CST Levi Morrison wrote:
> > IMPLEMENTATION STYLE #1: collectionof operator > > The style of implementation I like the most is a collectionof operator in > > parallel to the instance_of operator. It would be instanceof's plural > > brother, so > > > > $arg collectionof \SomeClass > > > > To pass the check $arg must be of the iterable psuedotype with all its > > values being a \SomeClass object. > > > > This is all well and good, but collection integrity checks are usually > > going to be looking to see if all the members are the same scalar. > > > > $arg collectionof string > > > > For language consistency we would need to allow instance_of to do the > > same, > > which it currently does not. > > > > $arg instanceof string > > > > This does create duplication with the is_* family of functions, but > > instanceof already overlaps is_a heavily. > > I see no value to this operator being applied to non-array > traversables. Our iterators cannot always be reliably rewound, such as > when using generators. Checking that the generator returns only > strings would consume all the input and would therefore be useless.
I concur. I'd actually ask what the intended goal is here. Is this to ensure that everything coming OUT of a collection is a given type, or that everything put IN a collection is a given type? Asserting that "at this point in time, everything in this collection is a given type" is honestly fairly useless unless it's enforced to stay that way. I can assert that "this array currently contains only strings", but the very next line could change that. Unless we go the generics-lite route and allow code to declare $x = array<string> (or whatever), in which case it becomes "enforce on the way in", and now we never need to check it because we know that only strings can be put in the array. Plus, as Levi says, asserting that all values in a non-array iterable are a given type requires a full scan, which is often a destructive operation. The better solution IMO is to allow for objects that enforce type on write, and for *different* objects that enforce type on read, even if lazy. That is (pseudo code, do not take literally): $a = new Collection<string>; $a[] = 5; // This will TypeError Which we can *almost* get now, but it causes an interface mismatch error: class Strings extends ArrayObject { public function offsetSet(string $k, $v) { parent::offsetSet($k, $v); } } But that's essentially the logic we'd want. On the read side (which you'd want for a generator or similar), the logic you'd want is essentially: class Ints extends ArrayObject { public function current() : int { return parent::current(); } } Which lints fine, but when I tested it just now returns strings quite happily without a type error, which seems wrong to me. (Why is it doing that, and is it a bug?) In any case, in neither situation is collectionof particularly useful. What is useful is a syntax-level "this collection will only allow type X to be added" specification and a "this iterable will always return type X" specification, both of which would generate a runtime TypeError or a compile- time error if it could be detected. --Larry Garfield

Christoph Becker

8 years ago
On 26.01.2018 at 00:28, Larry Garfield wrote:
> On the read side (which you'd want for a generator or similar), the logic > you'd want is essentially: > > class Ints extends ArrayObject { > public function current() : int { > return parent::current(); > } > } > > Which lints fine, but when I tested it just now returns strings quite happily > without a type error, which seems wrong to me. (Why is it doing that, and is > it a bug?)
ArrayObject does not have a `current` method[1] (it does not implement Iterator, but rather IteratorAggregate), so it is never called, and therefore the `parent::current()` call doesn't error, from what I can tell. For what it's worth, overriding the `offsetGet` method works as expected[2]. [1] <https://3v4l.org/0o55V> [2] <https://3v4l.org/1njNa>
-- Christoph M. Becker

Larry Garfield

8 years ago
On Friday, January 26, 2018 8:43:20 AM CST Christoph M. Becker wrote:
> On 26.01.2018 at 00:28, Larry Garfield wrote: > > On the read side (which you'd want for a generator or similar), the logic > > you'd want is essentially: > > > > class Ints extends ArrayObject { > > > > public function current() : int { > > > > return parent::current(); > > > > } > > > > } > > > > Which lints fine, but when I tested it just now returns strings quite > > happily without a type error, which seems wrong to me. (Why is it doing > > that, and is it a bug?) > > ArrayObject does not have a `current` method[1] (it does not implement > Iterator, but rather IteratorAggregate), so it is never called, and > therefore the `parent::current()` call doesn't error, from what I can tell. > > For what it's worth, overriding the `offsetGet` method works as expected[2]. > > [1] <https://3v4l.org/0o55V> > [2] <https://3v4l.org/1njNa>
Well that would explain it. I had tried offsetGet() as well, but realized after reading this that I just flat out fatfingered it. Ah well. Still, the rest of my post still applies. --Larry Garfield