rfc:any_all_on_iterable
Differences
This shows you the differences between two versions of the page.
Next revision | Previous revision | ||
rfc:any_all_on_iterable [2020/08/30 16:13] – created tandre | rfc:any_all_on_iterable [2021/06/13 14:36] (current) – tandre | ||
---|---|---|---|
Line 1: | Line 1: | ||
- | ====== PHP RFC: any() and all() on iterables ====== | + | ====== PHP RFC: PHP\iterable\any() and all() on iterables ====== |
- | * Version: 0.1 | + | * Version: 0.6 |
* Date: 2020-08-30 | * Date: 2020-08-30 | ||
* Author: Tyson Andre, tandre@php.net | * Author: Tyson Andre, tandre@php.net | ||
- | * Status: | + | * Status: |
* First Published at: https:// | * First Published at: https:// | ||
* Implementation: | * Implementation: | ||
Line 9: | Line 9: | ||
===== Introduction ===== | ===== Introduction ===== | ||
- | The primitives any() and all() are a common part of many programming languages and help in avoiding verbosity or unnecessary abstractions. | + | The primitives |
- | - https:// | ||
- | - https:// | ||
- | - https:// | ||
- | - https:// | ||
- | For example, the following code could be shortened significantly. | + | - Haskell: https:// |
+ | - JavaScript: https:// | ||
+ | - Python: https:// | ||
+ | - Ruby: https:// | ||
+ | - Java 8(Stream): https:// | ||
+ | - C++: https:// | ||
+ | |||
+ | For example, the following code could be shortened significantly: | ||
<code php> | <code php> | ||
- | // Old version | + | // The old version |
- | $satisifes_predicate | + | $satisifies_predicate |
- | foreach ($items as $item) { | + | foreach ($item_list |
+ | // Performs DB operations or external service requests, stops on first match by design. | ||
if (API:: | if (API:: | ||
$satisfies_predicate = true; | $satisfies_predicate = true; | ||
Line 34: | Line 38: | ||
<code php> | <code php> | ||
- | // New version is much shorter, readable, and easier to review, | + | // The new version is much shorter, readable, and easier to review, |
- | // without creating temporary variables or helper functions that are used in only one place | + | // without creating temporary variables or helper functions that are used in only one place. |
- | if (!any($items, fn($x) => API:: | + | |
+ | // Performs DB operations or external service requests, stops on first match by design. | ||
+ | if (!\PHP\iterable\any($item_list, fn($item) => API:: | ||
throw new APIException(" | throw new APIException(" | ||
} | } | ||
Line 43: | Line 49: | ||
===== Proposal ===== | ===== Proposal ===== | ||
- | Add the following | + | Add the functions |
+ | (The namespace '' | ||
+ | |||
+ | **The implementation is equivalent to the following polyfill:** | ||
<code php> | <code php> | ||
- | /** Determines whether any element of the iterable satisfies the predicate. */ | + | namespace PHP\iterable; |
- | function any(iterable $input, ?callable $callback = null) { | + | |
+ | /** | ||
+ | * Determines whether any element of the iterable satisfies the predicate. | ||
+ | * | ||
+ | * | ||
+ | * If the value returned by the callback is truthy | ||
+ | * (e.g. true, non-zero number, non-empty array, truthy object, etc.), | ||
+ | * this is treated as satisfying the predicate. | ||
+ | * | ||
+ | * @param iterable $input | ||
+ | * @param null|callable(mixed): | ||
+ | */ | ||
+ | function any(iterable $input, ?callable $callback = null): bool { | ||
foreach ($input as $v) { | foreach ($input as $v) { | ||
if ($callback !== null ? $callback($v) : $v) { | if ($callback !== null ? $callback($v) : $v) { | ||
Line 55: | Line 76: | ||
return false; | return false; | ||
} | } | ||
- | /** Determines whether all elements of the iterable satisfy the predicate */ | + | </ |
- | function all(iterable $input, ?callable $callback = null) { | + | <code php> |
+ | /** | ||
+ | * Determines whether all elements of the iterable satisfy the predicate. | ||
+ | * | ||
+ | * If the value returned by the callback is truthy | ||
+ | * (e.g. true, non-zero number, non-empty array, truthy object, etc.), | ||
+ | * this is treated as satisfying the predicate. | ||
+ | * | ||
+ | * @param iterable $input | ||
+ | * @param null|callable(mixed): | ||
+ | */ | ||
+ | function all(iterable $input, ?callable $callback = null): bool { | ||
foreach ($input as $v) { | foreach ($input as $v) { | ||
if (!($callback !== null ? $callback($v) : $v)) { | if (!($callback !== null ? $callback($v) : $v)) { | ||
Line 66: | Line 98: | ||
</ | </ | ||
- | This proposal recommends adding '' | + | This proposal recommends adding '' |
- | - New contributors to projects wouldn' | + | - New contributors to projects wouldn' |
- If this was provided only in userland, there' | - If this was provided only in userland, there' | ||
- If the standard library provided it, then polyfills for newer php functionality could adopt this as well, making cleaner code easier to write. | - If the standard library provided it, then polyfills for newer php functionality could adopt this as well, making cleaner code easier to write. | ||
+ | |||
+ | ==== Implementation Details ==== | ||
+ | |||
+ | When '' | ||
+ | |||
+ | When '' | ||
+ | |||
+ | |||
+ | <code php> | ||
+ | php > var_export(PHP\iterable\any([false])); | ||
+ | false | ||
+ | php > var_export(PHP\iterable\any([true])); | ||
+ | true | ||
+ | php > var_export(PHP\iterable\any([0])); | ||
+ | false | ||
+ | php > var_export(PHP\iterable\any([1])); | ||
+ | true | ||
+ | php > var_export(PHP\iterable\any([0], | ||
+ | false | ||
+ | php > var_export(PHP\iterable\any([1], | ||
+ | true | ||
+ | |||
+ | php > var_export(PHP\iterable\all([true, | ||
+ | true | ||
+ | php > var_export(PHP\iterable\all([1, | ||
+ | true | ||
+ | php > var_export(PHP\iterable\all([true, | ||
+ | false | ||
+ | php > var_export(PHP\iterable\all([1, | ||
+ | false | ||
+ | php > var_export(PHP\iterable\all([1, | ||
+ | false | ||
+ | </ | ||
+ | |||
+ | ==== Secondary Vote: any()/all() or any_value()/ | ||
+ | |||
+ | A secondary vote will be held on whether to name this '' | ||
+ | |||
+ | PHP is unique in that the primitive array-like type '' | ||
+ | Existing function names vary in whether the fact that they only act on values is explicitly included in the name. | ||
+ | |||
+ | Many other programming languages have gone with a short name for the default of checking if a value is in a collection. | ||
+ | |||
+ | < | ||
+ | The primitives '' | ||
+ | |||
+ | - Haskell: https:// | ||
+ | - JavaScript: https:// | ||
+ | - Python: https:// | ||
+ | - Ruby: https:// | ||
+ | - Java 8(Stream): https:// | ||
+ | - C++: https:// | ||
+ | </ | ||
+ | |||
+ | Benefits of a shorter name: | ||
+ | |||
+ | - Conciseness for the most common use case of checking whether a predicate is true for any/all values of an iterable array/ | ||
+ | - Consistency with some other functions such as '' | ||
+ | - Potential to use $flags to extend this to support less common use cases like '' | ||
+ | |||
+ | Benefits of a longer name: | ||
+ | |||
+ | - A longer name would be more descriptive and make it easier to understand what code is doing. | ||
+ | - This makes it likely that in the future for iterable functionality, | ||
===== Backward Incompatible Changes ===== | ===== Backward Incompatible Changes ===== | ||
- | Any userland functions called '' | + | Any userland functions called '' |
+ | Because the [[https:// | ||
===== Proposed PHP Version(s) ===== | ===== Proposed PHP Version(s) ===== | ||
Line 102: | Line 199: | ||
</ | </ | ||
- | ===== Proposed Voting Choices | + | ==== Add first($iterable, |
- | Add '' | + | https:// |
- | ===== References ===== | + | < |
+ | If it took the default value as well it could return that (to distinguish the absence of a result from null matching the predicate). 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: | ||
- | - https:// | + | <code php> |
- | - https://externals.io/message/103357 | + | $none = new stdClass; |
+ | $element | ||
+ | if ($element === $none) { | ||
+ | // nothing found | ||
+ | } | ||
+ | </code> | ||
+ | </blockquote> | ||
+ | Calling it '' | ||
+ | |||
+ | ===== Discussion ===== | ||
+ | |||
+ | ==== Alternative names ==== | ||
+ | |||
+ | '' | ||
+ | |||
+ | < | ||
+ | I suggest slightly different signatures, assuming we stay value-oriented: | ||
+ | |||
+ | <code php> | ||
+ | // ...omitted | ||
+ | |||
+ | // with named parameters | ||
+ | all_values(of: | ||
+ | any_value(of: | ||
+ | |||
+ | // without named parameters | ||
+ | all_values([1, | ||
+ | any_value([0, | ||
+ | </ | ||
+ | The naming clarifies what any and all are about--the values--and leaves room for naming functions that are key or key/value oriented. | ||
+ | </ | ||
+ | |||
+ | '' | ||
+ | |||
+ | < | ||
+ | The main thing I'm concerned about is that once we start extending this | ||
+ | area (I assume that any & all are not going to be the last additions in | ||
+ | this space) we will quickly run into function names that are either too | ||
+ | generic or outright collide. For example, what if we want to add an | ||
+ | iterator-based version of range()? Do we really want to be forced to pull | ||
+ | a Python and call it xrange()? That's about as good as real_range()... | ||
+ | |||
+ | As such, I think it's important to prefix these somehow, though I don't | ||
+ | care strongly how. Could be iter_all() or iterable_all(). We might even | ||
+ | make it iterator_all() if we also adjust other existing iterator_* | ||
+ | functions to accept iterables. I'd also be happy with iter\all() or | ||
+ | iterable\all(), | ||
+ | </ | ||
+ | |||
+ | Because '' | ||
+ | This also allows potentially supporting '' | ||
+ | |||
+ | Initially, the proposal was to add this in the global scope as '' | ||
+ | |||
+ | ==== Add find_first() instead? ==== | ||
+ | |||
+ | < | ||
+ | I was actually working on this sort of thing recently. Technically, | ||
+ | you can support all, any, and first by using a single function: | ||
+ | |||
+ | <code php> | ||
+ | function find_first(iterable $of, callable($value, | ||
+ | </ | ||
+ | |||
+ | It converts the '' | ||
+ | for each key/value pair until one returns true, and then always | ||
+ | returns the iterator at the current position. | ||
+ | |||
+ | This allows you to know both key and value when making a decision. | ||
+ | By returning an iterator the caller can get both key and value. | ||
+ | By returning an iterator it can handle both the empty case and not | ||
+ | found cases with '' | ||
+ | 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. | ||
+ | </ | ||
+ | |||
+ | |||
+ | ===== Vote ===== | ||
+ | |||
+ | Add '' | ||
+ | |||
+ | Voting started on 2021-02-08 and ended on 2021-02-22. | ||
+ | |||
+ | <doodle title=" | ||
+ | * Yes | ||
+ | * No | ||
+ | </ | ||
+ | |||
+ | \\ The following secondary vote will be used to decide between '' | ||
+ | |||
+ | <doodle title=" | ||
+ | * any()/all() | ||
+ | * any_value()/ | ||
+ | </ | ||
+ | |||
+ | ==== Straw Poll ==== | ||
+ | |||
+ | <doodle title=" | ||
+ | * Too small in scope | ||
+ | * Object to the choice of namespace | ||
+ | * Prefer the global namespace | ||
+ | * Confused about the implementation | ||
+ | * Prefer userland solutions | ||
+ | * Other | ||
+ | * Voted for this RFC | ||
+ | </ | ||
+ | |||
+ | ===== References ===== | ||
+ | |||
+ | - https:// | ||
+ | - https:// | ||
+ | - https:// | ||
+ | - [[rfc: | ||
+ | - [[rfc: | ||
+ | - [[rfc: | ||
+ | - [[rfc: | ||
===== Rejected Features ===== | ===== Rejected Features ===== | ||
- | Keep this updated with features that were discussed | + | |
+ | Adding flags like [[https:// | ||
+ | |||
+ | ===== Changelog ====== | ||
+ | |||
+ | * 0.3: Add more quotes | ||
+ | * 0.4: Change name to '' | ||
+ | * 0.5: Add straw poll | ||
+ | * 0.6: Add examples of how this works, add in missing return type, clarify treatment of predicate $callback return type |
rfc/any_all_on_iterable.1598803986.txt.gz · Last modified: 2020/08/30 16:13 by tandre