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.
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.
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.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
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.xThese 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.
$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
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.xThese 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
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.
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.xThese 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.
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
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.xThese 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
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.
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]
On Thu, Jan 25, 2018 at 2:16 PM, Rowan Collins rowan.collins@gmail.com
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.
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]
On Thu, Jan 25, 2018 at 2:10 PM, Rowan Collins rowan.collins@gmail.com
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.
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: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
So, given
$a collectionof string
the operator returns whether or not $ais, 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.
So, given
$a collectionof string
the operator returns whether or not $ais, 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
$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:
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.
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.
Michael Morris tendoaki@gmail.com schrieb am Fr., 26. Jan. 2018, 02:06:
$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:
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.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 tendoaki@gmail.com schrieb am Fr., 26. Jan. 2018, 02:06:
$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:
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.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.
$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 iterATOR. Two different things.
[...]
The similarity of the names is regrettable, but it's already in place and
can't be changed at this point.
I think you misunderstood Niklas's point. Your example showed the syntax
"SomeClass<string>" with an iterator/iterable specific meaning, which would
mean we couldn't later use this syntax for generics. With generics, "$b
instanceof SomeClass<string>" would mean "is the class of $b, or one of its
parents or interfaces, a generic template SomeClass<T> specialised on the
type string"; that would be incompatible with your proposed meaning of "$b can
be iterated and contains only strings".
The plain form "iterable<string>" would co-exist fine with generics, and
"Iterator<string>" could be kept compatible if a generic interface
"Iterator<T>" was added when generics came along, so we wouldn't be tying
our hands by adding those.
Regards,
Rowan Collins
[IMSoP]
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]
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.
The
is_callable()
above prevents iteration over generators, potentially consuming them
for no purpose.
Unfortunately, it doesn't: it's not the generator definition that you'll be passed, but the Generator object returned when you run it, and that is not callable https://3v4l.org/rknQJ
(The definition is more like a constructor, and I wish it didn't look so much like a normal function in declaration and use.)
Nor are generators the only non-rewindable iterables you need to worry about, so really the only options you have are to only inspect arrays, or to add a big fat warning that the function may consume your iterable (or enter an infinite loop).
Regards,
--
Rowan Collins
[IMSoP]
On Mon, Jan 29, 2018 at 3:26 PM, Rowan Collins rowan.collins@gmail.com
wrote:
On 29 January 2018 19:14:43 GMT+00:00, Michael Morris tendoaki@gmail.com
wrote:The
is_callable()
above prevents iteration over generators, potentially consuming them
for no purpose.Unfortunately, it doesn't: it's not the generator definition that you'll
be passed, but the Generator object returned when you run it, and that is
not callable https://3v4l.org/rknQJ(The definition is more like a constructor, and I wish it didn't look so
much like a normal function in declaration and use.)Nor are generators the only non-rewindable iterables you need to worry
about, so really the only options you have are to only inspect arrays, or
to add a big fat warning that the function may consume your iterable (or
enter an infinite loop).
Ok, so make no attempt to keep people from shooting themselves in the foot
eh?
Fine, is this otherwise workable?
On Mon, Jan 29, 2018 at 3:26 PM, Rowan Collins rowan.collins@gmail.com
wrote:
Nor are generators the only non-rewindable iterables you need to worry
about, so really the only options you have are to only inspect arrays, or
to add a big fat warning that the function may consume your iterable (or
enter an infinite loop).
Sorry about the double reply. What about using count()
as a guard? While
there might be some countable consumables, my understand of generators is
they are often used where no prior count is available. Arrays and
ArrayObjects are countable.
On Mon, Jan 29, 2018 at 3:26 PM, Rowan Collins rowan.collins@gmail.com
wrote:
Nor are generators the only non-rewindable iterables you need to worry
about, so really the only options you have are to only inspect arrays, or
to add a big fat warning that the function may consume your iterable (or
enter an infinite loop).Sorry about the double reply. What about using
count()
as a guard? While
there might be some countable consumables, my understand of generators is
they are often used where no prior count is available. Arrays and
ArrayObjects are countable.
It's totally legit to have an object that is a consumable iterable that is
also countable. (So many -ables in that sentence...)
Really, these functions would be useful only on arrays, period. To allow them
on anything else is just dangerous, and on other iterables there are better,
more robust approaches (as discussed elsewhere in this thread).
As you've demonstrated they're also quite compact and effective to do in user-
space, so unless there's a massive performance difference of moving them to C
they don't seem all that appropriate to add to the language directly.
--Larry Garfield
On Mon, Jan 29, 2018 at 6:16 PM, Larry Garfield larry@garfieldtech.com
wrote:
Really, these functions would be useful only on arrays, period. To allow
them
on anything else is just dangerous, and on other iterables there are
better,
more robust approaches (as discussed elsewhere in this thread).As you've demonstrated they're also quite compact and effective to do in
user-
space, so unless there's a massive performance difference of moving them
to C
they don't seem all that appropriate to add to the language directly.--Larry Garfield
Didn't you personally raise the issue of hard dependencies doing this sort
of functionality creates? Implementable in userland or not, this is pretty
low level functionality.
Hmm.. If limited to arrays only then array_filter with count is pretty
close to what we want.
assert(count(array_filter($a, is_string)) === count($a));
That's not optimal though - granted assert code doesn't have to be
optimal, but that's still a wordy construction.
So what about this function
bool array_test(array $array [, callable $callback [, mixed $flag = 0]])
Iterates over $array, passing the value of each element to $callback. If
the callback returns true for all elements the array_test returns true,
otherwise it returns false. It is not guaranteed to iterate over the entire
array - it stops when the first false result is returned by the callback.
Flags are the same as array_filter.
So...
assert( array_test($a, 'is_string'), TypeError);
assert( array_test($a, function($v) { return $v instanceof SomeClass; }),
TypeError);
Not what I came into the thread looking for, but it's not bad.
s
So what about this function
bool array_test(array $array [, callable $callback [, mixed $flag = 0]])
Iterates over $array, passing the value of each element to $callback. If
the callback returns true for all elements the array_test returns true,
otherwise it returns false. It is not guaranteed to iterate over the entire
array - it stops when the first false result is returned by the callback.
Flags are the same as array_filter.So...
assert( array_test($a, 'is_string'), TypeError);
assert( array_test($a, function($v) { return $v instanceof SomeClass; }),
TypeError);Not what I came into the thread looking for, but it's not bad.
See https://github.com/php/php-src/pull/1385 which apparently has been
abandoned, unfortunately.
--
Christoph M. Becker
On Mon, Jan 29, 2018 at 6:16 PM, Larry Garfield larry@garfieldtech.com
wrote:
Really, these functions would be useful only on arrays, period. To allow
them
on anything else is just dangerous, and on other iterables there are
better,
more robust approaches (as discussed elsewhere in this thread).As you've demonstrated they're also quite compact and effective to do in
user-
space, so unless there's a massive performance difference of moving them
to C
they don't seem all that appropriate to add to the language directly.--Larry Garfield
Didn't you personally raise the issue of hard dependencies doing this sort
of functionality creates? Implementable in userland or not, this is pretty
low level functionality.
I don't recall doing so in this thread, but I most likely have on some other
issue. It's the sort of comment that I would make. :-)
However, that's for a very commonly used function where just inlining it is
prohibitive. As you've demonstrated below, this functionality is easily a one
liner, and it's a one-liner in an assert statement most likely.
Hmm.. If limited to arrays only then array_filter with count is pretty
close to what we want.assert(count(array_filter($a, is_string)) === count($a));
That's not optimal though - granted assert code doesn't have to be
optimal, but that's still a wordy construction.
Personally I think that's fine, and doesn't need a language-level utility
function to wrap it any further.
(Yes, that's a subjective metric. So are most statements about what
should[n't] be included in the language itself. Others are welcome to
disagree, but this falls below my threshold of necessity.)
--Larry Garfield
On Tue, Jan 30, 2018 at 12:13 PM, Larry Garfield larry@garfieldtech.com
wrote:
On Mon, Jan 29, 2018 at 6:16 PM, Larry Garfield larry@garfieldtech.com
wrote:
Really, these functions would be useful only on arrays, period. To
allow
them
on anything else is just dangerous, and on other iterables there are
better,
more robust approaches (as discussed elsewhere in this thread).As you've demonstrated they're also quite compact and effective to do
in
user-
space, so unless there's a massive performance difference of moving
them
to C
they don't seem all that appropriate to add to the language directly.--Larry Garfield
Didn't you personally raise the issue of hard dependencies doing this
sort
of functionality creates? Implementable in userland or not, this is
pretty
low level functionality.I don't recall doing so in this thread, but I most likely have on some
other
issue. It's the sort of comment that I would make. :-)
It was during the discussion on the Drupal list on adding runtime
assertions, the Inspector class in general. Curiously, no one on that list
raised the problem of allowing Inspector to traverse any Traversable. At
the time Drupal was PHP 5.4 min, so no generators to consider, and I guess
the other implications skipped everyone's mind.
Personally I think that's fine, and doesn't need a language-level utility
function to wrap it any further.
We'll just have to disagree then. After all, roughly half the array_*
functions can be user implemented, as can roughly half the string functions
(though how to do so is often cryptic to beginners).
array_test might not be the best name. Anyone got alternatives other than
the one in the abandoned RFC mentioned elsewhere in this thread (Not that
that one is bad, but if this is the direction to go some input on what to
actually call the thing would be nice.)?
On Mon, Jan 29, 2018 at 6:16 PM, Larry Garfield larry@garfieldtech.com
wrote:
Really, these functions would be useful only on arrays, period. To allow
them
on anything else is just dangerous, and on other iterables there are
better,
more robust approaches (as discussed elsewhere in this thread).As you've demonstrated they're also quite compact and effective to do in
user-
space, so unless there's a massive performance difference of moving them
to C
they don't seem all that appropriate to add to the language directly.--Larry Garfield
Didn't you personally raise the issue of hard dependencies doing this sort
of functionality creates? Implementable in userland or not, this is pretty
low level functionality.I don't recall doing so in this thread, but I most likely have on some other
issue. It's the sort of comment that I would make. :-)However, that's for a very commonly used function where just inlining it is
prohibitive. As you've demonstrated below, this functionality is easily a one
liner, and it's a one-liner in an assert statement most likely.Hmm.. If limited to arrays only then array_filter with count is pretty
close to what we want.assert(count(array_filter($a, is_string)) === count($a));
That's not optimal though - granted assert code doesn't have to be
optimal, but that's still a wordy construction.Personally I think that's fine, and doesn't need a language-level utility
function to wrap it any further.(Yes, that's a subjective metric. So are most statements about what
should[n't] be included in the language itself. Others are welcome to
disagree, but this falls below my threshold of necessity.)--Larry Garfield
I agree with this sentiment from Larry. However I do think there is a
function we could add:
function every(callable $predicate, traversable $input): bool {
foreach ($input as $value) {
if (!$predicate($value)) {
return false;
}
}
return true;
}
In which case the code would become:
assert(every('is_string', $input));
Of course, when passing a traversable the caller must to be careful as
it will be consumed. If they don't want that to happen they can
convert it to an array (possibly with iterator_to_array).
The reason this "every" function is better than "array_test" is
because it is more general and works with more things than just types.
The name "every" I borrowed from Clojure, but it's known by many
names; in Java it is known as "allMatch".
On Tue, Jan 30, 2018 at 11:13 AM, Larry Garfield larry@garfieldtech.com
wrote:On Mon, Jan 29, 2018 at 6:16 PM, Larry Garfield <larry@garfieldtech.com
wrote:
Really, these functions would be useful only on arrays, period. To
allow
them
on anything else is just dangerous, and on other iterables there are
better,
more robust approaches (as discussed elsewhere in this thread).As you've demonstrated they're also quite compact and effective to do
in
user-
space, so unless there's a massive performance difference of moving
them
to C
they don't seem all that appropriate to add to the language directly.--Larry Garfield
Didn't you personally raise the issue of hard dependencies doing this
sort
of functionality creates? Implementable in userland or not, this is
pretty
low level functionality.I don't recall doing so in this thread, but I most likely have on some
other
issue. It's the sort of comment that I would make. :-)However, that's for a very commonly used function where just inlining it
is
prohibitive. As you've demonstrated below, this functionality is easily
a one
liner, and it's a one-liner in an assert statement most likely.Hmm.. If limited to arrays only then array_filter with count is pretty
close to what we want.assert(count(array_filter($a, is_string)) === count($a));
That's not optimal though - granted assert code doesn't have to be
optimal, but that's still a wordy construction.Personally I think that's fine, and doesn't need a language-level utility
function to wrap it any further.(Yes, that's a subjective metric. So are most statements about what
should[n't] be included in the language itself. Others are welcome to
disagree, but this falls below my threshold of necessity.)--Larry Garfield
I agree with this sentiment from Larry. However I do think there is a
function we could add:function every(callable $predicate, traversable $input): bool { foreach ($input as $value) { if (!$predicate($value)) { return false; } } return true; }
In which case the code would become:
assert(every('is_string', $input));
Of course, when passing a traversable the caller must to be careful as
it will be consumed. If they don't want that to happen they can
convert it to an array (possibly with iterator_to_array).The reason this "every" function is better than "array_test" is
because it is more general and works with more things than just types.
The name "every" I borrowed from Clojure, but it's known by many
names; in Java it is known as "allMatch".--
I'm fine with this - I just want the inspector to be part of the language
so that a hidden dependency isn't required.
I'm fine with this - I just want the inspector to be part of the
language
so that a hidden dependency isn't required.
I may be being dumb, but I don't get why you keep referring to this as a "hidden dependency". It's a dependency, for sure, but why is it any more "hidden" than any other utility library?
Regards,
--
Rowan Collins
[IMSoP]
The Dependency injector isn't aware of the reference and is unable to
modify it in any way. I've been taught to avoid such dependencies, and to
avoid utility libraries which create such dependencies.
On Tue, Feb 6, 2018 at 2:54 PM, Rowan Collins rowan.collins@gmail.com
wrote:
On 6 February 2018 20:18:07 GMT+00:00, Michael Morris tendoaki@gmail.com
wrote:I'm fine with this - I just want the inspector to be part of the
language
so that a hidden dependency isn't required.I may be being dumb, but I don't get why you keep referring to this as a
"hidden dependency". It's a dependency, for sure, but why is it any more
"hidden" than any other utility library?Regards,
--
Rowan Collins
[IMSoP]
On Tue, Feb 6, 2018 at 2:54 PM, Rowan Collins rowan.collins@gmail.com
wrote:I may be being dumb, but I don't get why you keep referring to this
as a
"hidden dependency". It's a dependency, for sure, but why is it any
more
"hidden" than any other utility library?
The Dependency injector isn't aware of the reference and is unable to
modify it in any way. I've been taught to avoid such dependencies, and
to
avoid utility libraries which create such dependencies.
OK, I see what you mean. But surely it doesn't really have much relevance here, because a function or operator from the language's standard library is just as invisible to dependency injection. In a sense, it's still a utility library, just one that comes bundled with the language rather than installed separately.
Indeed, a built-in function is even more restricted in what you can do with it than a userland one - for instance, you could replace the userland implementation simply by declaring your version first and not including the original, but you can't overwrite a built-in function without hacking into the engine.
I'm not saying this undermines the proposal for adding some extra array functions or operators, but dependency injection and modification are certainly not advantages of doing so.
Regards,
--
Rowan Collins
[IMSoP]
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]
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.
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
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 functioncurrent()
: 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
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 theparent::current()
call doesn't error, from what I can tell.For what it's worth, overriding the
offsetGet
method works as expected[2].
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