rfc:any_all_on_iterable

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
Next revision
Previous revision
Last revisionBoth sides next revision
rfc:any_all_on_iterable [2020/12/19 18:42] – link to RFC announcement tandrerfc:any_all_on_iterable [2021/02/22 15:28] tandre
Line 1: Line 1:
-====== PHP RFC: any() and all() on iterables ====== +====== PHP RFC: PHP\iterable\any() and all() on iterables ====== 
-  * Version: 0.2+  * Version: 0.6
   * Date: 2020-08-30   * Date: 2020-08-30
   * Author: Tyson Andre, tandre@php.net   * Author: Tyson Andre, tandre@php.net
-  * Status: Under Discussion+  * Status: Declined
   * First Published at: https://wiki.php.net/rfc/any_all_on_iterable   * First Published at: https://wiki.php.net/rfc/any_all_on_iterable
   * Implementation: https://github.com/php/php-src/pull/6053   * Implementation: https://github.com/php/php-src/pull/6053
Line 10: Line 10:
  
 The primitives ''any()'' and ''all()'' are a common part of many programming languages and help in avoiding verbosity or unnecessary abstractions. The primitives ''any()'' and ''all()'' are a common part of many programming languages and help in avoiding verbosity or unnecessary abstractions.
 +
  
   - Haskell: https://hackage.haskell.org/package/base-4.14.0.0/docs/Prelude.html#v:any   - Haskell: https://hackage.haskell.org/package/base-4.14.0.0/docs/Prelude.html#v:any
   - JavaScript: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some   - JavaScript: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some
   - Python: https://docs.python.org/3/library/functions.html#all   - Python: https://docs.python.org/3/library/functions.html#all
 +  - Ruby: https://apidock.com/ruby/Enumerable/any%3F
   - Java 8(Stream): https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#allMatch-java.util.function.Predicate-   - Java 8(Stream): https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#allMatch-java.util.function.Predicate-
 +  - C++: https://www.cplusplus.com/reference/algorithm/any_of/
  
 For example, the following code could be shortened significantly: For example, the following code could be shortened significantly:
Line 20: Line 23:
 <code php> <code php>
 // The old version // The old version
-$satisifes_predicate = false;+$satisifies_predicate = false;
 foreach ($item_list as $item) { foreach ($item_list as $item) {
     // Performs DB operations or external service requests, stops on first match by design.     // Performs DB operations or external service requests, stops on first match by design.
Line 39: Line 42:
  
 // Performs DB operations or external service requests, stops on first match by design. // Performs DB operations or external service requests, stops on first match by design.
-if (!any($item_list, fn($item) => API::satisfiesCondition($item))) {+if (!\PHP\iterable\any($item_list, fn($item) => API::satisfiesCondition($item))) {
     throw new APIException("No matches found");     throw new APIException("No matches found");
 } }
Line 46: Line 49:
 ===== Proposal ===== ===== Proposal =====
  
-Add the functions ''any(iterable $input, ?callable $callback = null): bool'' and ''all(...)'' to php's standard function set.+Add the functions ''PHP\iterable\any(iterable $input, ?callable $callback = null): bool'' and ''all(...)'' to PHP's standard library'function set. 
 +(The namespace ''PHP\iterable'' was preferred in [[rfc:any_all_on_iterable_straw_poll_namespace#vote|a straw poll that was previously sent out]]) 
 + 
 +**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):mixed $callback 
 + */ 
 +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 58: Line 76:
     return false;     return false;
 } }
-/** Determines whether all elements of the iterable satisfy the predicate */ +</code> 
-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):mixed $callback 
 + */ 
 +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 69: Line 98:
 </code> </code>
  
-This proposal recommends adding ''any()'' and ''all()'' to the standard library instead of a PECL or composer library for the following reasons+This proposal recommends adding ''PHP\iterable\any()'' and ''PHP\iterable\all()'' to the standard library instead of a PECL or composer library for the following reasons
  
-  - New contributors to projects wouldn't know about ''any()'' and ''all()'' if it was reimplemented in various composer libraries or util.php files with different semantics/names and only occasionally used.+  - New contributors to projects wouldn't know about ''any()'' and ''all()'' if those functions were reimplemented in various composer libraries or util.php files with different semantics/names and only occasionally used.
   - If this was provided only in userland, there'd be low adoption and code such as the above example (API::somePredicate()) would remain common.   - If this was provided only in userland, there'd be low adoption and code such as the above example (API::somePredicate()) would remain common.
   - 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 ''any()'' or ''all()'' are called with an iterable and a predicate, it internally checks if the value returned by the predicate is truthy (e.g. true, non-zero numbers, non-empty arrays, truthy objects, etc.)
 +
 +When ''any()'' or ''all()'' are called with only an iterable, it is equivalent to checking if any/all of the arguments are truthy. This is equivalent to calling ''any()''/''all()'' with ''fn ($x) => $x'', which is equivalent to calling it with ''fn($x) => (bool)$x''.
 +
 +
 +<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], fn($x) => $x));
 +false
 +php > var_export(PHP\iterable\any([1], fn($x) => $x));
 +true
 +
 +php > var_export(PHP\iterable\all([true, true, true], fn($x) => $x));
 +true
 +php > var_export(PHP\iterable\all([1, 2, 3], fn($x) => $x));
 +true
 +php > var_export(PHP\iterable\all([true, true, false], fn($x) => $x));
 +false
 +php > var_export(PHP\iterable\all([1, 2, 0], fn($x) => $x));
 +false
 +php > var_export(PHP\iterable\all([1, 2, 0]);
 +false
 +</code>
 +
 +==== Secondary Vote: any()/all() or any_value()/all_values() ====
 +
 +A secondary vote will be held on whether to name this ''any()''/''all()'' or ''any_value()''/''all_values()''
 +
 +PHP is unique in that the primitive array-like type ''array'' type is also a dictionary, making the keys often significant (strings, numeric identifiers, etc).
 +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.
 +
 +<blockquote>
 +The primitives ''any()'' and ''all()'' are a common part of many programming languages and help in avoiding verbosity or unnecessary abstractions.
 +
 +  - Haskell: https://hackage.haskell.org/package/base-4.14.0.0/docs/Prelude.html#v:any
 +  - JavaScript: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some
 +  - Python: https://docs.python.org/3/library/functions.html#all
 +  - Ruby: https://apidock.com/ruby/Enumerable/any%3F
 +  - Java 8(Stream): https://docs.oracle.com/javase/8/docs/api/java/util/stream/Stream.html#allMatch-java.util.function.Predicate-
 +  - C++: https://www.cplusplus.com/reference/algorithm/any_of/
 +</blockquote>
 +
 +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/object.
 +  - Consistency with some other functions such as ''array_reduce()'', ''array_unique()'', ''in_array()'', ''next()'' that use values for their underlying implementation (i.e. not being named ''array_reduce_values()'', ''next_value()'', etc.)
 +  - Potential to use $flags to extend this to support less common use cases like ''ARRAY_FILTER_USE_KEY''/''ARRAY_FILTER_USE_BOTH'' without adding more global functions
 +
 +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, PHP will add multiple functions such as ''any_key()''/(''any_entry'' or ''any_key_value'') instead of using $flags (which will be simpler to statically analyze or infer types for - in rare cases the argument $flags passed to ''array_filter($values, $callback, $flags)'' is an unknown dynamic value). \\ Adding a constant such as ''flags: PHP\iterable\USE_KEY'' may make the code longer. \\ Note that adding the name ''any()'' for values of iterables doesn't prevent PHP from adding ''any_key()'' for checking keys of iterables in the future, either (my personal preference would be to add ''any_key()'' regardless of whether ''any()''/''any_value()'' was added).
  
 ===== Backward Incompatible Changes ===== ===== Backward Incompatible Changes =====
  
-Any userland functions called ''any()'' and ''all()'' in the global namespace without a ''!function_exists()'' check would encounter duplicate function errors.+Any userland functions called ''PHP\iterable\any()'' and ''PHP\iterable\all()'' in the global namespace without a ''!function_exists()'' check would encounter duplicate function errors
 +Because the [[https://www.php.net/manual/en/language.namespaces.rationale.php|PHP namespace is reserved for internal use by PHP]], this is unlikely.
  
 ===== Proposed PHP Version(s) ===== ===== Proposed PHP Version(s) =====
Line 130: Line 224:
  
 ''any_value()'' or ''all_values()'' have been suggested as alternative names: https://github.com/php/php-src/pull/6053#issuecomment-684164832 ''any_value()'' or ''all_values()'' have been suggested as alternative names: https://github.com/php/php-src/pull/6053#issuecomment-684164832
 +
 +<blockquote>
 +I suggest slightly different signatures, assuming we stay value-oriented:
 +
 +<code php>
 +// ...omitted
 +
 +// with named parameters
 +all_values(of: [1, 3, 5, 7], satisfy: 'is_odd');
 +any_value(of: [0, 2, 4, 6], satisfies: 'is_prime');
 +
 +// without named parameters
 +all_values([1, 3, 5, 7], 'is_odd');
 +any_value([0, 2, 4, 6], 'is_prime');
 +</code>
 +The naming clarifies what any and all are about--the values--and leaves room for naming functions that are key or key/value oriented.
 +</blockquote>
  
 ''iter_any()'' or ''iterable_any()'' have also been suggested as alternative names. ''iter_any()'' or ''iterable_any()'' have also been suggested as alternative names.
 +
 +<blockquote>
 +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(), but that gets us back into namespacing discussions :)
 +</blockquote>
  
 Because ''any()'' and ''all()'' are potentially commonly used functions in the same way as ''count(Countable|array)'' and always return booleans, I preferred a short name over longer names. Because ''any()'' and ''all()'' are potentially commonly used functions in the same way as ''count(Countable|array)'' and always return booleans, I preferred a short name over longer names.
 This also allows potentially supporting ''int $flags = 0'' in the future, similar to what was done for ''array_filter()''. This also allows potentially supporting ''int $flags = 0'' in the future, similar to what was done for ''array_filter()''.
  
-Functions that act on iterables and return Traversables/iterables may still benefit from adding collections of functionality under a naming pattern such as `iter\function_name()` or `iterable_function_name()`, but that is out of the scope of this RFC. https://github.com/nikic/iter is an example of a composer package with some common iteration primitives that may be useful to consider adding, possibly returning arrays instead of Generators for performance/predictability reasons.+Initially, the proposal was to add this in the global scope as ''iterable_all()'' and ''iterable_any()''.
  
 ==== Add find_first() instead? ==== ==== Add find_first() instead? ====
Line 163: Line 289:
  
  
-===== Proposed Voting Choices =====+===== Vote =====
  
-Add ''any(iterable $input, ?callable $callback = null)'' and ''all(...)'' (yes/no, requiring 2/3 majority)+Add ''PHP\iterable\any(iterable $input, ?callable $callback = null): bool'' and ''PHP\iterable\all(iterable $input, ?callable $callback = null): bool'' (yes/no, requiring 2/3 majority) 
 + 
 +Voting started on 2021-02-08 and ended on 2021-02-22. 
 + 
 +<doodle title="Add PHP\iterable\any() and all() to PHP?" voteType="single" auth="tandre" closed="true"> 
 +   * Yes 
 +   * No 
 +</doodle> 
 + 
 + \\ The following secondary vote will be used to decide between ''any()''/''all()'' and ''any_value()''/''all_values()'' as the name within the ''PHP\iterable'' namespace. See [[#secondary_voteanyall_or_any_valueall_values|the discussion section for the benefits/drawbacks of those names]]. 
 + 
 +<doodle title="Names to use: any()/all() or any_value()/all_values()" voteType="single" auth="tandre" closed="false"> 
 +   * any()/all() 
 +   * any_value()/all_values() 
 +</doodle> 
 + 
 +==== Straw Poll ==== 
 + 
 +<doodle title="Reasons for voting against this RFC" voteType="multi" auth="tandre" closed="false"> 
 +   * 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 
 +</doodle>
  
 ===== References ===== ===== References =====
Line 172: Line 324:
   - https://externals.io/message/103357 "[PATCH] Implementing array_every() and array_any()"   - https://externals.io/message/103357 "[PATCH] Implementing array_every() and array_any()"
   - https://externals.io/message/111756 "[RFC] Global functions any() and all() on iterables"   - https://externals.io/message/111756 "[RFC] Global functions any() and all() on iterables"
 +  - [[rfc:any_all_on_iterable_straw_poll|Straw poll: Naming for *any() and *all() on iterables]]
 +  - [[rfc:any_all_on_iterable_straw_poll_namespace|Straw poll: Using namespaces for *any() and *all() on iterables]]
  
 ===== Rejected Features ===== ===== Rejected Features =====
  
 Adding flags like [[https://php.net/array_filter|''array_filter()'']] was left out of this RFC due to debate over how often it would be used in practice and moved to future scope. Adding flags like [[https://php.net/array_filter|''array_filter()'']] was left out of this RFC due to debate over how often it would be used in practice and moved to future scope.
 +
 +===== Changelog ======
 +
 +  * 0.3: Add more quotes
 +  * 0.4: Change name to ''PHP\iterable\all'' and ''PHP\iterable\any'', add a secondary vote on ''any/all'' vs ''any_value()/all_values()''
 +  * 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.txt · Last modified: 2021/06/13 14:36 by tandre