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
rfc:any_all_on_iterable [2020/12/19 18:42] – link to RFC announcement tandrerfc: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.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]] (older poll) 
 +  - [[rfc:namespaces_in_bundled_extensions|RFC: Namespaces in bundled PHP extensions]] (started after voting closed) 
 +  - [[rfc:cachediterable_straw_poll|Straw poll: Namespace to use for CachedIterable and iterable functionality]] (newer poll)
 ===== 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.1608403348.txt.gz · Last modified: 2020/12/19 18:42 by tandre