Proposal: Adding functions any(iterable $input, ?callable $cb = null,int $use_flags=0) and all(...)

  111711
August 29, 2020 20:24 tysonandre775@hotmail.com (tyson andre)
Hi internals,

The primitives any() and all() are a common part of many programming languages and help in avoiding verbosity or unnecessary abstractions.

- https://hackage.haskell.org/package/base-4.14.0.0/docs/Prelude.html#v:any
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some
- https://docs.python.org/3/library/functions.html#all
- https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#allMatch-java.util.function.Predicate-

For example, the following code could be shortened significantly

```
// Old version
$satisifes_predicate = false;
foreach ($items as $item) {
    if (API::satisfiesCondition($item)) {
        $satisfies_predicate = true;
        break;
    }
}
if (!$satisfies_predicate) {
    throw new APIException("No matches found");
}

// New version is much shorter and readable
if (!any($items, fn($x) => API::satisfiesCondition($x))) {
    throw new APIException("No matches found");
}
```

That example doesn't have any suitable helpers already in the standard library.
Using array_filter would unnecessarily call satisfiesCondition even after the first item was found,
and array_search doesn't take a callback.

A proposed implementation is https://github.com/php/php-src/pull/6053 - it takes similar flags and param orders to array_filter().

Previous discussion was in https://externals.io/message/103357#103373

- New contributors to projects wouldn't know about any() and all() if it was reimplemented with different semantics and only occasionally used
   (e.g. MyArrayUtil::any()) in various projects)
- If this was provided only in userland, there'd be low adoption and code such as the first example would remain common.
   If the standard library provided it, then polyfills would as well, making cleaner code easier to write.

Thanks,
- Tyson
  111712
August 29, 2020 20:39 alexinbeijing@gmail.com (Alex)
I like it!

What is the $use_flags parameter for?

On Sat, Aug 29, 2020 at 10:24 PM tyson andre <tysonandre775@hotmail.com>
wrote:

> Hi internals, > > The primitives any() and all() are a common part of many programming > languages and help in avoiding verbosity or unnecessary abstractions. > > - > https://hackage.haskell.org/package/base-4.14.0.0/docs/Prelude.html#v:any > - > https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some > - https://docs.python.org/3/library/functions.html#all > - > https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#allMatch-java.util.function.Predicate- > > For example, the following code could be shortened significantly > > ``` > // Old version > $satisifes_predicate = false; > foreach ($items as $item) { > if (API::satisfiesCondition($item)) { > $satisfies_predicate = true; > break; > } > } > if (!$satisfies_predicate) { > throw new APIException("No matches found"); > } > > // New version is much shorter and readable > if (!any($items, fn($x) => API::satisfiesCondition($x))) { > throw new APIException("No matches found"); > } > ``` > > That example doesn't have any suitable helpers already in the standard > library. > Using array_filter would unnecessarily call satisfiesCondition even after > the first item was found, > and array_search doesn't take a callback. > > A proposed implementation is https://github.com/php/php-src/pull/6053 - > it takes similar flags and param orders to array_filter(). > > Previous discussion was in https://externals.io/message/103357#103373 > > - New contributors to projects wouldn't know about any() and all() if it > was reimplemented with different semantics and only occasionally used > (e.g. MyArrayUtil::any()) in various projects) > - If this was provided only in userland, there'd be low adoption and code > such as the first example would remain common. > If the standard library provided it, then polyfills would as well, > making cleaner code easier to write. > > Thanks, > - Tyson > -- > PHP Internals - PHP Runtime Development Mailing List > To unsubscribe, visit: https://www.php.net/unsub.php > >
  111713
August 29, 2020 21:11 josh@joshbruce.dev (Josh Bruce)
Hello!

I’m still finding my understanding of actual internals, so can only comment from a PHP developer perspective.

The “any” check is just to if anything in the itrerable passes the predicate, yeah??

What I find myself doing more often is wanting the first thing to satisfy the predicate - a “first” function, if you will.

This could skip the step of iterating to find of something satisfies the predicate. Then iterating again to get one or more items that do satisfy it.

Trying to think of a use case where I would want to check, but not do anything with that knowledge.

Again, might not be completely following. 

Cheers,
Josh

> On Aug 29, 2020, at 3:39 PM, Alex <alexinbeijing@gmail.com> wrote: > > I like it! > > What is the $use_flags parameter for? > >> On Sat, Aug 29, 2020 at 10:24 PM tyson andre <tysonandre775@hotmail.com> >> wrote: >> Hi internals, >> The primitives any() and all() are a common part of many programming >> languages and help in avoiding verbosity or unnecessary abstractions. >> - >> https://hackage.haskell.org/package/base-4.14.0.0/docs/Prelude.html#v:any >> - >> https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some >> - https://docs.python.org/3/library/functions.html#all >> - >> https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#allMatch-java.util.function.Predicate- >> For example, the following code could be shortened significantly >> ``` >> // Old version >> $satisifes_predicate = false; >> foreach ($items as $item) { >> if (API::satisfiesCondition($item)) { >> $satisfies_predicate = true; >> break; >> } >> } >> if (!$satisfies_predicate) { >> throw new APIException("No matches found"); >> } >> // New version is much shorter and readable >> if (!any($items, fn($x) => API::satisfiesCondition($x))) { >> throw new APIException("No matches found"); >> } >> ``` >> That example doesn't have any suitable helpers already in the standard >> library. >> Using array_filter would unnecessarily call satisfiesCondition even after >> the first item was found, >> and array_search doesn't take a callback. >> A proposed implementation is https://github.com/php/php-src/pull/6053 - >> it takes similar flags and param orders to array_filter(). >> Previous discussion was in https://externals.io/message/103357#103373 >> - New contributors to projects wouldn't know about any() and all() if it >> was reimplemented with different semantics and only occasionally used >> (e.g. MyArrayUtil::any()) in various projects) >> - If this was provided only in userland, there'd be low adoption and code >> such as the first example would remain common. >> If the standard library provided it, then polyfills would as well, >> making cleaner code easier to write. >> Thanks, >> - Tyson >> -- >> PHP Internals - PHP Runtime Development Mailing List >> To unsubscribe, visit: https://www.php.net/unsub.php
  111715
August 29, 2020 23:48 tysonandre775@hotmail.com (tyson andre)
> The “any” check is just to if anything in the iterable passes the predicate, yeah?? > > What I find myself doing more often is wanting the first thing to satisfy the predicate - a “first” function, if you will.
There's multiple things such a function could return - The key of the entry (could be false/null), the value of the entry (could be false/null), or a combination of the key and value (rarely what we want). I'm not aware of any exact matches for that - array_filter processes all values, and array_search doesn't accept a callback. `array_search_callback()` could be added, but I don't plan to expand the scope of this RFC and there are multiple ways to do that.
> This could skip the step of iterating to find of something satisfies the predicate. Then iterating again to get one or more items that do satisfy it.. > > Trying to think of a use case where I would want to check, but not do anything with that knowledge.
Anywhere where you'd want to check for membership in a short-circuiting way, similar to situations where `||` or `&&` would be called, but with a variable number of callbacks or repeating the same operation multiple times. It also reduces the indentation and line count needed ``` $this->assertTrue(all($valueList, fn($v) => $v->isValid())); all($startupCallbacks, 'call_user_func') || exit("startup failed"); if (any($fieldList, fn($field) => in_array($field->name, ['fooEnabled', 'fooEnabled2']))) { doFoo(); } ``` - Tyson
  111719
August 30, 2020 14:22 josh@joshbruce.dev (Josh Bruce)
Think I’m following. 

The compelling feature here for me is the idea of an “array walker” that can be broken out of.

all() could be and() at which point this should be equivalent.

$collection = [1, 2, 3];

If (count($collection) === count(array_filter($collection, “is_int”))

any() could be or() at which point this should be equivalent, short of the breaking early.

If (count(array_filter($collection, “is_int”)) > 0)

The benefit I see of the first (using count === count) is that I could store the result of the filter to use later based on the passing of the condition without iterating again. (See str_replace, where the function returns the result, while making count available to the caller as well: https://www.php.net/manual/en/function.str-replace.php <https://www.php.net/manual/en/function.str-replace.php> - using pass by reference.)

The drawback of the second (using count > 0) is that I have to iterate over the entire collection despite only needing one element to pass the predicate.

Would there also be an xor() or equivalent?? one() or something??

If (count(array_filter($collection, “is_int”)) === 1)

Here’s an implementation of each() I made that allows for breaking by the caller using a passed by ref third argument in the closure: https://github.com/8fold/php-shoop/blob/2a8b6fc41c545ff71690562ac2f35a12457b1514/src/Traits/EachImp.php <https://github.com/8fold/php-shoop/blob/2a8b6fc41c545ff71690562ac2f35a12457b1514/src/Traits/EachImp.php> - I’ve since deprecated that functionality and not sure if I’ll be bringing that functionality back because of frustrations and philosophical arguments with myself. :)

Part of me wishes the “all()” and “any()” names were more descriptive of what’s going on. 

When I call for “all” in a database or some ORM, I *get* all - I’m not verifying “all pass [a predicate]”.

Cheers,
Josh

> On Aug 29, 2020, at 6:48 PM, tyson andre <tysonandre775@hotmail.com> wrote: > >> The “any” check is just to if anything in the iterable passes the predicate, yeah?? >> >> What I find myself doing more often is wanting the first thing to satisfy the predicate - a “first” function, if you will. > > There's multiple things such a function could return - The key of the entry (could be false/null), the value of the entry (could be false/null), or a combination of the key and value (rarely what we want). > > I'm not aware of any exact matches for that - array_filter processes all values, and array_search doesn't accept a callback. > `array_search_callback()` could be added, but I don't plan to expand the scope of this RFC and there are multiple ways to do that. > >> This could skip the step of iterating to find of something satisfies the predicate. Then iterating again to get one or more items that do satisfy it. >> >> Trying to think of a use case where I would want to check, but not do anything with that knowledge. > > Anywhere where you'd want to check for membership in a short-circuiting way, similar to situations where `||` or `&&` would be called, > but with a variable number of callbacks or repeating the same operation multiple times. > It also reduces the indentation and line count needed > > ``` > $this->assertTrue(all($valueList, fn($v) => $v->isValid())); > > all($startupCallbacks, 'call_user_func') || exit("startup failed"); > > if (any($fieldList, fn($field) => in_array($field->name, ['fooEnabled', 'fooEnabled2']))) { > doFoo(); > } > ``` > > - Tyson > > -- > PHP Internals - PHP Runtime Development Mailing List > To unsubscribe, visit: https://www.php.net/unsub.php >
  111724
August 30, 2020 15:40 tysonandre775@hotmail.com (tyson andre)
Hi Josh Bruce,

> The compelling feature here for me is the idea of an “array walker” that can be broken out of. > > all() could be and() at which point this should be equivalent.
Yes, you can write `all($set, $predicate_fn)` as `!any($set, fn($x) => !$predicate_fn($x))` or some other common primitive such as and(), but as an end user or a reviewer of code, I really don't want to do the latter, which is why I proposed adding both of these. (also see below snippet)
> Would there also be an xor() or equivalent?? one() or something?? > > If (count(array_filter($collection, “is_int”)) === 1)
I don't plan to expand the scope of the RFC more - there are various features such as none() or `exactly_n(int $n, $iterable, $callback)` I'm also not proposing. RFC votes require a 2/3 majority and I'd think more would advocate doing one() in userland than any()
> Here’s an implementation of each() I made that allows for breaking by the caller using a passed by ref third argument in the closure: https://github.com/8fold/php-shoop/blob/2a8b6fc41c545ff71690562ac2f35a12457b1514/src/Traits/EachImp.php - I’ve since deprecated that functionality and not sure if I’ll be bringing that functionality back because of frustrations and philosophical arguments with myself. :)
Modifying a reference instead could be done to achieve the same result as setting a reference boolean in the cases where you needed to know what element it was. ``` if (any($values, function ($v) use (&$result) { if ($v->satisfiesPredicate()) {$result = $v; return true;} return false; }) ) { process($result); } ```
> Part of me wishes the “all()” and “any()” names were more descriptive of what’s going on.  > > When I call for “all” in a database or some ORM, I *get* all - I’m not verifying “all pass [a predicate]”.
The naming is based on mathematical notation. - any($list, $predicate) "Determines whether any element of the iterable satisfies the predicate." - all($list, $predicate) "Determines whether all elements of the iterable satisfy the predicate." every() and some() are alternative names, but all()/any() are my preference and appear more commonly used elsewhere. - https://hackage.haskell.org/package/base-4.14.0.0/docs/Prelude.html#v:any - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some - https://docs.python.org/3/library/functions.html#all - https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#allMatch-java.util.function.Predicate- Thanks, - Tyson
  111714
August 29, 2020 23:18 tysonandre775@hotmail.com (tyson andre)
Hi Alex,

> I like it! > > What is the $use_flags parameter for?
It's for deciding what parameters to pass to callback, like https://www.php..net/array_filter - the reflection name should probably just be $flag Occasionally, the key might be useful to check against, e.g. the identifier of an item in a map, or SplObjectStorage. - the default is `any($iterable, fn ($value) => ...)` - or `any($iterable, fn ($key) => expr, ARRAY_FILTER_USE_KEY)` - or `any($iterable, fn ($value, $key) => expr, ARRAY_FILTER_USE_BOTH)` Thanks, - Tyson
  111717
August 30, 2020 06:31 mike@newclarity.net (Mike Schinkel)
> On Aug 29, 2020, at 7:18 PM, tyson andre <tysonandre775@hotmail.com> wrote: > > Hi Alex, > >> I like it! >> >> What is the $use_flags parameter for? > > It's for deciding what parameters to pass to callback, like https://www.php.net/array_filter - the reflection name should probably just be $flag > > Occasionally, the key might be useful to check against, e.g. the identifier of an item in a map, or SplObjectStorage. > > - the default is `any($iterable, fn ($value) => ...)` > - or `any($iterable, fn ($key) => expr, ARRAY_FILTER_USE_KEY)` > - or `any($iterable, fn ($value, $key) => expr, ARRAY_FILTER_USE_BOTH)`
Doesn't the occasional required use of long constant names like ARRAY_FILTER_USE_KEY and ARRAY_FILTER_USE_BOTH somewhat negative the benefit of a more concise syntax? I know that many existing functions in PHP have a collection of long constant names that can be passed, but is that a style of programming that PHP really wants to continue to proliferate, or would it not be better to address that use-case in a different manner? (Admittedly doing so might require having this feature dependent on other critical path language features.) One approach might be if PHP could allow for skipped parameters in callables — where I am using a single underscore to indicate an unused parameter (GoLang has a "blank identifier" for similar purposes so there is some prior art to consider) — e.g: - the default is `any($iterable, fn ( $value ) => ...)` - or `any($iterable, fn ( _, $key ) => expr )` - or `any($iterable, fn ( $value, $key ) => expr )` PHP could handle it more generically, is possible, or recognize this and branch to different implementations of any() and all() if the goal is to optimize performance by only passing one value in key-only or value-only calls. Optionally we could use null instead: - the default is `any($iterable, fn ($value) => ...)` - or `any($iterable, fn (null, $key) => expr )` - or `any($iterable, fn ($value, $key) => expr )` -Mike [1] https://www.geeksforgeeks.org/what-is-blank-identifierunderscore-in-golang/#:~:text=_(underscore)%20in%20Golang%20is,Blank%20Identifier. <https://www.geeksforgeeks.org/what-is-blank-identifierunderscore-in-golang/#:~:text=_(underscore)%20in%20Golang%20is,Blank%20Identifier.>
  111718
August 30, 2020 12:24 josh@joshbruce.dev (Josh Bruce)
>> >> - the default is `any($iterable, fn ($value) => ...)` >> - or `any($iterable, fn ($key) => expr, ARRAY_FILTER_USE_KEY)` >> - or `any($iterable, fn ($value, $key) => expr, ARRAY_FILTER_USE_BOTH)` > > Doesn't the occasional required use of long constant names like ARRAY_FILTER_USE_KEY and ARRAY_FILTER_USE_BOTH somewhat negative the benefit of a more concise syntax?
For what it’s worth, the constants do give at least two benefits: 1. Flexibility for adding functionality without signature change. 2. Readability at the call site even if the output isn’t different in most cases. example(true, true, false); example(USE_BLUE, DROP_FIRST... With names arguments in 8 we do get helped, I think. mb_case_switch had 3 flags in the beginning, now gas 6 - if I’m remembering and interpreting the docs correctly. Cheers, Josh
  111727
August 30, 2020 16:31 tysonandre775@hotmail.com (tyson andre)
Hi Mike Schinkel,

> Doesn't the occasional required use of long constant names like ARRAY_FILTER_USE_KEY and ARRAY_FILTER_USE_BOTH somewhat negative the benefit of a more concise syntax? > > I know that many existing functions in PHP have a collection of long constant names that can be passed, but is that a style of programming that PHP really wants to continue to proliferate, or would it not be better to address that use-case in a different manner? (Admittedly doing so might require having this feature dependent on other critical path language features.) > > One approach might be if PHP could allow for skipped parameters in callables — where I am using a single underscore to indicate an unused parameter (GoLang has a "blank identifier" for similar purposes so there is some prior art to consider) — e.g: > > - the default is `any($iterable, fn ( $value ) => ...)` > - or `any($iterable, fn ( _, $key ) => expr )` > - or `any($iterable, fn ( $value, $key ) => expr )` > > PHP could handle it more generically, is possible, or recognize this and branch to different implementations of any() and all() if the goal is to optimize performance by only passing one value in key-only or value-only calls. > > Optionally we could use null instead: > > - the default is `any($iterable, fn ($value) => ...)` > - or `any($iterable, fn (null, $key) => expr )` > - or `any($iterable, fn ($value, $key) => expr )`
Supporting "blank identifiers" is out of the scope of this RFC, and would have some implementation concerns of how it'd work with named parameters and how backtrace generation would be affected. I don't think there'd be a significant performance improvement over not using parameters. I'm taking $flag out of the rfc because there's many proposed alternatives for doing this, and checking values of an iterable is the most common use case. Repeating the response to Marco Pivetta, I find that checking parameter counts would be inconsistent with how other internal functions accepting callbacks such as `array_filter` already behave. It would also be unexpected if users or tooling wrap callbacks with other callbacks. Developers would also expect `func_get_args()` to work. ``` $predicate(...$args)); } // PHP can't distinguish between these when given `fn(...$args)` // - would throw if passed 1 parameter my_none($values, fn($value, $key) => $value == true || $key == 'x'); // - would throw if passed 2 parameters (too many) my_none($values, 'is_string'); ``` Cheers, - Tyson
  111775
September 2, 2020 03:34 mike@newclarity.net (Mike Schinkel)
> On Aug 30, 2020, at 12:31 PM, tyson andre <tysonandre775@hotmail.com> wrote: > > Hi Mike Schinkel, > >> Doesn't the occasional required use of long constant names like ARRAY_FILTER_USE_KEY and ARRAY_FILTER_USE_BOTH somewhat negative the benefit of a more concise syntax? >> >> I know that many existing functions in PHP have a collection of long constant names that can be passed, but is that a style of programming that PHP really wants to continue to proliferate, or would it not be better to address that use-case in a different manner? (Admittedly doing so might require having this feature dependent on other critical path language features.) >> >> One approach might be if PHP could allow for skipped parameters in callables — where I am using a single underscore to indicate an unused parameter (GoLang has a "blank identifier" for similar purposes so there is some prior art to consider) — e.g: >> >> - the default is `any($iterable, fn ( $value ) => ...)` >> - or `any($iterable, fn ( _, $key ) => expr )` >> - or `any($iterable, fn ( $value, $key ) => expr )` >> >> PHP could handle it more generically, is possible, or recognize this and branch to different implementations of any() and all() if the goal is to optimize performance by only passing one value in key-only or value-only calls. >> >> Optionally we could use null instead: >> >> - the default is `any($iterable, fn ($value) => ...)` >> - or `any($iterable, fn (null, $key) => expr )` >> - or `any($iterable, fn ($value, $key) => expr )` > > Supporting "blank identifiers" is out of the scope of this RFC, > and would have some implementation concerns of how it'd work with named parameters > and how backtrace generation would be affected. I don't think there'd be a significant performance improvement over not using parameters. > > I'm taking $flag out of the rfc because there's many proposed alternatives for doing this, > and checking values of an iterable is the most common use case.
Acknowledged. If not a significant performance improvement why not pass both value and key to the callable in your RFC in case someone needs access to the key? Adding the key as a second parameter would not preclude passing a flag to modify behavior in a future version of PHP if that is the internals consensus. -Mike P.S. John Bafford recently suggested the use of void in foreach loops for essentially the same reason as I proposed with underscore or null (and frankly void is better than underscore or null): https://externals.io/message/111774 <https://externals.io/message/111774> If his RFC were to pass then that would open the door to using it here, right?
  111716
August 30, 2020 03:46 ocramius@gmail.com (Marco Pivetta)
Hey Tyson,
<http://ocramius.github.com/>


On Sat, Aug 29, 2020 at 10:24 PM tyson andre <tysonandre775@hotmail.com>
wrote:

> Hi internals, > > The primitives any() and all() are a common part of many programming > languages and help in avoiding verbosity or unnecessary abstractions. > > - > https://hackage.haskell.org/package/base-4.14.0.0/docs/Prelude.html#v:any > - > https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some > - https://docs.python.org/3/library/functions.html#all > - > https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#allMatch-java.util.function.Predicate- > > For example, the following code could be shortened significantly > > ``` > // Old version > $satisifes_predicate = false; > foreach ($items as $item) { > if (API::satisfiesCondition($item)) { > $satisfies_predicate = true; > break; > } > } > if (!$satisfies_predicate) { > throw new APIException("No matches found"); > } > > // New version is much shorter and readable > if (!any($items, fn($x) => API::satisfiesCondition($x))) { > throw new APIException("No matches found"); > } > ``` > > That example doesn't have any suitable helpers already in the standard > library. > Using array_filter would unnecessarily call satisfiesCondition even after > the first item was found, > and array_search doesn't take a callback. > > A proposed implementation is https://github.com/php/php-src/pull/6053 - > it takes similar flags and param orders to array_filter(). > > Previous discussion was in https://externals.io/message/103357#103373 > > - New contributors to projects wouldn't know about any() and all() if it > was reimplemented with different semantics and only occasionally used > (e.g. MyArrayUtil::any()) in various projects) > - If this was provided only in userland, there'd be low adoption and code > such as the first example would remain common. > If the standard library provided it, then polyfills would as well, > making cleaner code easier to write. > > Thanks, > - Tyson >
Would it make sense, instead of having a third boolean parameter (causing two parameters to be coupled together - already quite messy with existing array functions) for `any()` and `all()` to just detect if the given callback requires >1 parameter? That would make this much simpler. Marco Pivetta http://twitter.com/Ocramius http://ocramius.github.com/
  111726
August 30, 2020 16:22 tysonandre775@hotmail.com (tyson andre)
Hi Marco Pivetta,

> Would it make sense, instead of having a third boolean parameter (causing two parameters to be coupled together - already quite messy with existing array functions) for `any()` and `all()` to just detect if the given callback requires >1 parameter? > > That would make this much simpler.
I find that to be inconsistent with how other internal functions accepting callbacks such as `array_filter` behave. It would also be unexpected if users or tooling wrap callbacks with other callbacks. Developers would also expect `func_get_args()` to work. ``` $predicate(...$args)); } // PHP can't distinguish between these when given `fn(...$args)` // - would throw if passed 1 parameter my_none($values, fn($value, $key) => $value == true || $key == 'x'); // - would throw if passed 2 parameters (too many) my_none($values, 'is_string'); ``` Thanks, - Tyson
  111721
August 30, 2020 14:49 larry@garfieldtech.com ("Larry Garfield")
On Sat, Aug 29, 2020, at 3:24 PM, tyson andre wrote:
> Hi internals, > > The primitives any() and all() are a common part of many programming > languages and help in avoiding verbosity or unnecessary abstractions. > > - > https://hackage.haskell.org/package/base-4.14.0.0/docs/Prelude.html#v:any > - > https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some > - https://docs.python.org/3/library/functions.html#all > - > https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#allMatch-java.util.function.Predicate- > > For example, the following code could be shortened significantly > > ``` > // Old version > $satisifes_predicate = false; > foreach ($items as $item) { > if (API::satisfiesCondition($item)) { > $satisfies_predicate = true; > break; > } > } > if (!$satisfies_predicate) { > throw new APIException("No matches found"); > } > > // New version is much shorter and readable > if (!any($items, fn($x) => API::satisfiesCondition($x))) { > throw new APIException("No matches found"); > } > ``` > > That example doesn't have any suitable helpers already in the standard > library. > Using array_filter would unnecessarily call satisfiesCondition even > after the first item was found, > and array_search doesn't take a callback. > > A proposed implementation is https://github.com/php/php-src/pull/6053 - > it takes similar flags and param orders to array_filter(). > > Previous discussion was in https://externals.io/message/103357#103373 > > - New contributors to projects wouldn't know about any() and all() if > it was reimplemented with different semantics and only occasionally used > (e.g. MyArrayUtil::any()) in various projects) > - If this was provided only in userland, there'd be low adoption and > code such as the first example would remain common. > If the standard library provided it, then polyfills would as well, > making cleaner code easier to write. > > Thanks, > - Tyson
I like this, but I do not like the flags. I don't think they're at all useful. A lot of the other discussion in the thread seems to be needlessly complicating it, too. all() and any() only need return booleans. Their callbacks only need return booleans. That's the point. first() makes sense to add, and it would return the first value that matches. For the callback itself, there is work to, hopefully, add partial function application to 8.1. (No idea if it will be successful, but the effort is in progress.) If so, the upshot is that turning an arbitrary function into a single-parameter function becomes silly easy, which means functions like this can just expect a single parameter callback and be done with it. No need for extra-args or flags or whatnot. If you want to check the keys of an array, call array_keys() first and use that. if (any(array_keys($foo), fn($k) => $k %2)) { ... } all(), any(), and first() all sound like good things to include, but let's not over-complicate them. We can do better today than we could in 1999... --Larry Garfield
  111723
August 30, 2020 15:12 tysonandre775@hotmail.com (tyson andre)
> I like this, but I do not like the flags.  I don't think they're at all useful.  A lot of the other discussion in the thread seems to be needlessly complicating it, too. >  > all() and any() only need return booleans.  Their callbacks only need return booleans.  That's the point.  first() makes sense to add, and it would return the first value that matches.
What would first() return on failure? Would it throw (inefficient)? Would it set an optional output reference to distinguish between returning the value null from an iterable and the null from no matches? An alternative use for a global function called first() would be to simply return the first element of an iterable, similar to the proposed array_value_first for arrays
> For the callback itself, there is work to, hopefully, add partial function application to 8.1.  (No idea if it will be successful, but the effort is in progress.)  If so, the upshot is that turning an arbitrary function into a single-parameter function becomes silly easy, which means functions like this can just expect a single parameter callback and be done with it.  No need for extra-args or flags or whatnot.
> If you want to check the keys of an array, call array_keys() first and use that. > > `if (any(array_keys($foo), fn($k) => $k %2)) { ... }`
That doesn't work for a predicate on the keys of a generator, e.g. if you want to stop calling the generator once the short-circuiting happens. And there are occasionally situations where the predicate depends both on the key and the value `any($itemMap, fn($enabled, $itemId) => $enabled && meetsAdditionalCondition($itemId), ARRAY_FILTER_USE_BOTH)` Still, since there's debate over whether to even complicate this by filtering by key, and what the best way is to implement filtering by key, I'll probably take out the `int $flag = 0` part from the initial RFC. Those can be added in subsequent RFCs. Thanks, - Tyson
  111728
August 30, 2020 17:48 weirdan@gmail.com (Bruce Weirdan)
On Sun, Aug 30, 2020 at 6:13 PM tyson andre <tysonandre775@hotmail.com>
wrote:

> > I like this, but I do not like the flags. I don't think they're at all > useful. A lot of the other discussion in the thread seems to be needlessly > complicating it, too. > > > > all() and any() only need return booleans. Their callbacks only need > return booleans. That's the point. first() makes sense to add, and it > would return the first value that matches. > > What would first() return on failure? Would it throw (inefficient)? > Would it set an optional output reference to distinguish between returning > the value null from an iterable and the null from no matches? >
If it took the default value as well it could return that. While it's useful in itself it also would enable you to pass a marker object and check the identity of that to know if no matches have been found: $none = new stdClass; $element = first($collection, fn($elt) => ...); if ($element === $none) { // nothing found } -- Best regards, Bruce Weirdan mailto: weirdan@gmail.com
  111729
August 30, 2020 18:01 weirdan@gmail.com (Bruce Weirdan)
> On Sun, Aug 30, 2020 at 6:13 PM tyson andre <tysonandre775@hotmail.com> > wrote: > >> > I like this, but I do not like the flags. I don't think they're at all >> useful. A lot of the other discussion in the thread seems to be needlessly >> complicating it, too. >> > >> > all() and any() only need return booleans. Their callbacks only need >> return booleans. That's the point. first() makes sense to add, and it >> would return the first value that matches. >> >> What would first() return on failure? Would it throw (inefficient)? >> Would it set an optional output reference to distinguish between >> returning the value null from an iterable and the null from no matches? >> > > If it took the default value as well it could return that. While it's > useful in itself it also would enable you to pass a marker object and check > the identity of that to know if no matches have been found: > > $none = new stdClass; > $element = first($collection, fn($elt) => ...); > if ($element === $none) { > // nothing found > } >
Of course it should have been `$element = first($collection, fn($elt) =>..., $none);` -- Best regards, Bruce Weirdan mailto: weirdan@gmail.com
  111732
August 30, 2020 20:51 tysonandre775@hotmail.com (tyson andre)
Hi Bruce Weirdan,

> If it took the default value as well it could return that. While it's useful in itself it also would enable you to pass > a marker object and check the identity of that to know if no matches have been found: > > $none = new stdClass; > $element = first($collection, fn($elt) => ..., $none); > if ($element === $none) { > // nothing found > }
Oh, right, I'd forgotten about that option - that would work. There's still the question of what it would do without a predicate. Making the predicate mandatory in the initial proposal would help avoid the confusion of whether `first() should behave like array_filter() and any() and filter for the first truthy value. Calling it [iterable_]search_callback() or first_matching() or find() might help distinguish this from the reset()/end()/next()/prev() family of functions - not happy with any of my naming ideas Thanks, - Tyson
  111730
August 30, 2020 19:16 dik.takken@gmail.com (Dik Takken)
On 29-08-2020 22:24, tyson andre wrote:
> Hi internals, > > The primitives any() and all() are a common part of many programming languages and help in avoiding verbosity or unnecessary abstractions.
I would love to see this come to PHP. I also do a lot of Python development and I really like its operators module, which provides function equivalents to the intrinsic Python operators. Have a look: https://docs.python.org/3/library/operator.html Although the any() and all() functions are my favorites, having the full range of operator functions would be great. That could ultimately yield a proper counterpart to the range of array_* functions which support any iterable in stead of just arrays. Think of a keys() function that is a generalization of array_keys(). This reminds me of the iter library that Nikita created: https://github.com/nikic/iter So yes, this can also be done in user space. Having it built into the language has advantages though: * High adoption rates, making lots of existing PHP code more concise * Possibly better performance Regarding performance: Since we already have opcodes for handling operators, it may be possible to extend these to consume arbitrary numbers of operands from iterables. Then, an operator function like any() would compile into its equivalent operator opcode. Finally, that opcode can probably be JIT compiled into a tight loop in machine code. So a +1 from me for adding any() and all() but let us also consider the general problem of lacking iterable support in PHP. Perhaps that could be the subject of a followup RFC. Regards, Dik Takken
  111731
August 30, 2020 20:43 tysonandre775@hotmail.com (tyson andre)
Hi Dik Takken,

> I would love to see this come to PHP. I also do a lot of Python > development and I really like its operators module, which provides > function equivalents to the intrinsic Python operators. Have a look: > > https://docs.python.org/3/library/operator.html > > Although the any() and all() functions are my favorites, having the full > range of operator functions would be great. That could ultimately yield > a proper counterpart to the range of array_* functions which support any > iterable in stead of just arrays. Think of a keys() function that is a > generalization of array_keys().
Operator overloading was the subject of https://wiki.php.net/rfc/userspace_operator_overloading#vote which had a 38-28 vote, which didn't meet the 2/3 voting threshold.
> This reminds me of the iter library that Nikita created: > > https://github.com/nikic/iter > > So yes, this can also be done in user space. Having it built into the language has advantages though:
> > * High adoption rates, making lots of existing PHP code more concise > * Possibly better performance
Strangely, if I remember correctly, I've had better performance looping inline and calling closures than calling array_map when there were a large number of elements. I think opcache's inferences and the fact there's no switch from internals back to the php vm was part of the reason for it. (and the tracking of stack frames?) The readability's my main reason for making it native. I'd wondered if there'd be a benefit to preloading where we replace C functions with native php functions (optionally JITted) if there's an expected benefit, like HHVM does for parts of its standard library, but again out of scope.
> Regarding performance: Since we already have opcodes for handling > operators, it may be possible to extend these to consume arbitrary > numbers of operands from iterables. Then, an operator function like > any() would compile into its equivalent operator opcode. Finally, that > opcode can probably be JIT compiled into a tight loop in machine code.
That's definitely something I'd want to see for array_map/array_filter. There's the question of whether it'd be reasonable to omit stack frames or whether it's practical to create fake frames with parameter values for debug_backtrace(). Larry Garfield also did some work on python-like list comprehensions, though it's still in draft. Automatically rewriting array_map(), array_filter(), all(), etc. to list comprehensions and then to JITted code could see a nice performance benefit in benefit, but this is out of scope of the RFC I'm working on See https://externals.io/message/104637
> So a +1 from me for adding any() and all() but let us also consider the > general problem of lacking iterable support in PHP. Perhaps that could > be the subject of a followup RFC.
Part of the issue is what the return type would be. For example, reduce(iterable, callable, $initial = null) could work well, but what should map() do `map(callable, iterable $values): iterable` - should it preserve keys, should it return an array|Generator, array|CustomTraversable, something depending on the object type, etc. (Generator performance seems like it'd be worse than eager evaluation) Regards, Tyson
  111734
August 31, 2020 00:02 larry@garfieldtech.com ("Larry Garfield")
On Sun, Aug 30, 2020, at 3:43 PM, tyson andre wrote:
> Hi Dik Takken, > > > I would love to see this come to PHP. I also do a lot of Python > > development and I really like its operators module, which provides > > function equivalents to the intrinsic Python operators. Have a look: > > > > https://docs.python.org/3/library/operator.html > > > > Although the any() and all() functions are my favorites, having the full > > range of operator functions would be great. That could ultimately yield > > a proper counterpart to the range of array_* functions which support any > > iterable in stead of just arrays. Think of a keys() function that is a > > generalization of array_keys(). > > Operator overloading was the subject of > https://wiki.php.net/rfc/userspace_operator_overloading#vote > which had a 38-28 vote, which didn't meet the 2/3 voting threshold. > > > This reminds me of the iter library that Nikita created: > > > > https://github.com/nikic/iter > > > > So yes, this can also be done in user space. Having it built into the > language has advantages though: > > > > * High adoption rates, making lots of existing PHP code more concise > > * Possibly better performance > > Strangely, if I remember correctly, I've had better performance looping > inline and calling closures than calling array_map > when there were a large number of elements. I think opcache's > inferences and the fact there's no switch > from internals back to the php vm was part of the reason for it. > (and the tracking of stack frames?) > > The readability's my main reason for making it native. I'd wondered if > there'd be a benefit to preloading where we replace C functions with > native php functions (optionally JITted) if there's an expected benefit, > like HHVM does for parts of its standard library, but again out of > scope. > > > Regarding performance: Since we already have opcodes for handling > > operators, it may be possible to extend these to consume arbitrary > > numbers of operands from iterables. Then, an operator function like > > any() would compile into its equivalent operator opcode. Finally, that > > opcode can probably be JIT compiled into a tight loop in machine code. > > That's definitely something I'd want to see for array_map/array_filter. > There's the question of whether it'd be reasonable to omit stack frames > or whether it's practical to create fake frames with parameter values > for debug_backtrace(). > > Larry Garfield also did some work on python-like list comprehensions, > though it's still in draft. > Automatically rewriting array_map(), array_filter(), all(), etc. to > list comprehensions and then to JITted code > could see a nice performance benefit in benefit, but this is out of > scope of the RFC I'm working on > See https://externals.io/message/104637
I was just considering mentioning that. :-) I still would like to see comprehensions happen, but I'd need to partner with someone with more engine skill than I to actually implement it, plus the reception was somewhat lukewarm. I do think they're useful and would help with any/all/first type use cases, though. If someone reading this is interested, you know where to find me... --Larry Garfield
  111735
August 31, 2020 02:08 internals@lists.php.net ("Levi Morrison via internals")
I was actually working on this sort of thing recently. _Technically_,
you can support `all`, `any`, and `first` by using a single function:

    function find_first(iterable $of, callable($value, $key): bool
$thatSatistifes): Iterator

It converts the $iterable into an Iterator, then calls the callback
for each key/value pair until one returns true, and then always
returns the iterator at the current position.

1. This allows you to know both key and value when making a decision.
2. By returning an iterator the caller can get both key and value.
3. By returning an iterator it can handle both the empty case and not
found cases with $result->valid() === false.
4. By returning an iterator it might be useful for processing the
remainder of the list somehow.

I'm not sure that in practice it would be that friendly, but it's
worth pointing out for discussion at least.
  111757
September 1, 2020 00:03 tysonandre775@hotmail.com (tyson andre)
Hi Levi Morrison,

> I was actually working on this sort of thing recently. _Technically_, > you can support `all`, `any`, and `first` by using a single function: > >     function find_first(iterable $of, callable($value, $key): bool $thatSatistifes): Iterator
> > It converts the $iterable into an Iterator, then calls the callback > for each key/value pair until one returns true, and then always > returns the iterator at the current position. > > I'm not sure that in practice it would be that friendly, but it's > worth pointing out for discussion at least.
I'd find the `find($iterable, $callable, $default = null): mixed` to be more user-friendly, personally, and would worry about accidentally increasing memory usage if I left one of those iterables around as a saved value or a long-lived local variable. There's plenty of time until the php 8.1 feature freeze, so I don't feel like increasing the scope of this RFC (https://wiki.php.net/rfc/any_all_on_iterable) even though I would find some version of `find()` with a callback useful. Thanks, - Tyson