rfc:pipe-operator-v2
Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revisionNext revisionBoth sides next revision | ||
rfc:pipe-operator-v2 [2021/05/20 16:46] – Include better examples of comprehensions, PFA, and short functions. crell | rfc:pipe-operator-v2 [2021/07/04 01:04] – crell | ||
---|---|---|---|
Line 1: | Line 1: | ||
====== PHP RFC: Pipe Operator v2 ====== | ====== PHP RFC: Pipe Operator v2 ====== | ||
- | * Version: | + | * Version: |
* Date: 2020-04-20 | * Date: 2020-04-20 | ||
* Author: Larry Garfield < | * Author: Larry Garfield < | ||
Line 25: | Line 25: | ||
</ | </ | ||
- | That, however, is still rather verbose and requires defining intermediary variables, and thus either coming up with names for them or using generic placeholder names like `$x`. The result is still error prone and it is possible to get confused by the variable names without realizing it. | + | That, however, is still rather verbose and requires defining intermediary variables, and thus either coming up with names for them or using generic placeholder names like `$x`. The result is still error prone and it is possible to get confused by the variable names without realizing it. It's also not intuitively obvious that what's happening is passing the output of one function to the next. |
In OOP, it's common to answer "well use a fluent interface," | In OOP, it's common to answer "well use a fluent interface," | ||
Line 42: | Line 42: | ||
===== Proposal ===== | ===== Proposal ===== | ||
- | This RFC introduces a new operator | + | This RFC introduces a new operator |
<code php> | <code php> | ||
Line 57: | Line 57: | ||
$result = "Hello World" | $result = "Hello World" | ||
|> ' | |> ' | ||
- | |> 'explode' | + | |> 'str_split' |
- | |> fn($x) => array_map(fn($v) => ' | + | |> fn($x) => array_map(' |
|> fn($x) => array_filter($x, | |> fn($x) => array_filter($x, | ||
</ | </ | ||
Line 64: | Line 64: | ||
<code php> | <code php> | ||
$result = array_filter( | $result = array_filter( | ||
- | array_map(' | + | array_map(' |
- | | + | |
), fn($v) => $v != ' | ), fn($v) => $v != ' | ||
); | ); | ||
</ | </ | ||
- | Or, more practically, | + | The left-hand side of the pipe may be any value or expression. |
- | <code php> | + | ===== Language theory ===== |
- | $holiday | + | |
- | $result | + | |
- | |> fn($x) | + | |
- | |> fn($x) | + | |
- | |> fn($x) | + | |
- | </ | + | |
- | The left-hand side of the pipe may be any value or expression. | + | The pipe operator is a form of function composition. |
- | Functions that accept their first parameter by reference are supported, and will behave exactly as if they were called in the normal | + | It also cleanly enables |
- | The pipe operator evaluates immediately. | + | ===== Callable syntax ===== |
- | <code php> | + | Pipes support any callable on the right hand side, using any callable syntax supported by PHP now or in the future. |
- | $holiday = " | + | |
- | $new_function = fn($user) => $user | + | |
- | |> fn($x) => getShoppingList($x, ' | + | |
- | |> fn($x) => mostExpensiveItem($x, [' | + | |
- | |> fn($x) => getPromotions($x, | + | |
- | $new_function(getCurrentUser()); | + | Should a version of [[rfc: |
- | </ | + | |
- | ===== More robust example with PSR-7 ===== | + | The examples below largely assume that the first-class-callable RFC has passed, which as of this writing appears guaranteed. |
- | <code php> | + | Additionally, functions that return callables may be used to conveniently produce pipe-compatible callables. |
- | ServerRequest:: | + | |
- | |> ' | + | |
- | |> [$router, ' | + | |
- | |> fn($request) => $request-> | + | |
- | |> ' | + | |
- | |> ' | + | |
- | |> ' | + | |
- | </ | + | |
===== Alternate comprehension syntax ==== | ===== Alternate comprehension syntax ==== | ||
Line 119: | Line 99: | ||
yield $c($val); | yield $c($val); | ||
} | } | ||
- | } | + | }; |
} | } | ||
Line 126: | Line 106: | ||
return function(iterable $it) use ($c) { | return function(iterable $it) use ($c) { | ||
foreach ($it as $val) { | foreach ($it as $val) { | ||
- | if ($c($val) { | + | if ($c($val)) { |
yield $val; | yield $val; | ||
} | } | ||
} | } | ||
+ | }; | ||
+ | } | ||
+ | |||
+ | // count(), but runs out an iterator to do so. | ||
+ | function itcount(iterable $it) { | ||
+ | $count = 0; | ||
+ | foreach ($it as $v) { | ||
+ | $count++; | ||
} | } | ||
+ | return $count; | ||
} | } | ||
+ | </ | ||
+ | |||
+ | And now comprehension-like behavior can be written using pipes, without the need for a dedicated syntax. | ||
+ | |||
+ | <code php> | ||
+ | $list = [1, 2, 3, 4, 5]; | ||
- | // And now comprehension-like behavior can be written using pipes: | + | $new_list = $list |
- | $new_list = $list |> itmap(fn($x) => $x*2) |> itfilter(fn($x) => $x %2) |> 'iterator_to_array'; | + | |
+ | | ||
+ | | ||
</ | </ | ||
Any combination of map, filter, reduce, or other array-oriented operation can be wrapped up this way and added to a pipe chain, allowing a similar result to comprehensions without a one-off syntax, and can be mixed-and-matched with any other callable as appropriate. | Any combination of map, filter, reduce, or other array-oriented operation can be wrapped up this way and added to a pipe chain, allowing a similar result to comprehensions without a one-off syntax, and can be mixed-and-matched with any other callable as appropriate. | ||
+ | |||
+ | String-oriented functions would be equally easy to produce. | ||
+ | |||
+ | ===== Additional semantics ===== | ||
+ | |||
+ | Functions that accept their first parameter by reference are supported, and will behave exactly as if they were called in the normal " | ||
+ | |||
+ | When evaluating a pipe, the left-hand side is fully evaluated first, then the right-hand side, then the right-hand side is invoked using the left-hand side. That is, evaluation is strictly left-to-right. | ||
+ | |||
+ | The pipe operator evaluates immediately. | ||
+ | |||
+ | <code php> | ||
+ | $array_op = fn(iterable $list) => $list | ||
+ | |> itmap(fn($x) => $x * 2) | ||
+ | |> itfilter(fn($x) => $x % 3) | ||
+ | |> iterator_to_array(...); | ||
+ | | ||
+ | $result = $array_op([1, | ||
+ | </ | ||
+ | |||
+ | ===== Further examples ===== | ||
+ | |||
+ | Given the utilities above, the following examples would all be valid. | ||
+ | |||
+ | <code php> | ||
+ | // Take a string, sanitize it, | ||
+ | // split it to an array, | ||
+ | // upper-case everything, | ||
+ | // and remove the letter O. | ||
+ | $result = "Hello World" | ||
+ | |> htmlentities(...) | ||
+ | |> str_split(...) | ||
+ | |> itmap(strtoupper(...)) | ||
+ | |> itfilter(fn($v) => $v != ' | ||
+ | </ | ||
+ | |||
+ | The example from the start of this RFC could be written as: | ||
+ | |||
+ | <code php> | ||
+ | $holiday = " | ||
+ | $result = getCurrentUser() | ||
+ | |> getShoppingList(' | ||
+ | |> mostExpensiveItem([' | ||
+ | |> getPromotions($holiday); | ||
+ | </ | ||
+ | |||
+ | For a more robust example, the following routine would, given a directory, give a line count of all files in the directory tree that have a specific extension. | ||
+ | |||
+ | <code php> | ||
+ | function nonEmptyLines(\SplFileInfo $file): iterable { | ||
+ | try { | ||
+ | $object = $file-> | ||
+ | $object-> | ||
+ | yield from $object; | ||
+ | } catch (\Throwable $error) { | ||
+ | // File system error handling irrelevant for the moment. | ||
+ | } | ||
+ | }; | ||
+ | |||
+ | function getLineCount(string $directory, string $ext): int { | ||
+ | return new RecursiveDirectoryIterator(' | ||
+ | |> new RecursiveIteratorIterator(? | ||
+ | |> itfilter(fn ($file) => $file-> | ||
+ | |> itmap(nonEmptyLines(...)) | ||
+ | |> itcount(...) | ||
+ | ; | ||
+ | } | ||
+ | |||
+ | print getLineCount(' | ||
+ | </ | ||
===== Prior art ===== | ===== Prior art ===== | ||
Line 146: | Line 213: | ||
Portions of this RFC are nonetheless based on the previous iteration, and the author wishes to thank the v1 authors for their inspiration. | Portions of this RFC are nonetheless based on the previous iteration, and the author wishes to thank the v1 authors for their inspiration. | ||
+ | |||
+ | ===== Existing implementations ===== | ||
+ | |||
+ | Multiple user-space libraries exist in PHP that attempt to replicate pipe-like behavior. | ||
+ | |||
+ | * The PHP League has a [[https:// | ||
+ | * Laravel includes a [[https:// | ||
+ | * Various blogs speak of "the Pipeline Pattern" | ||
+ | |||
+ | Those libraries would be mostly obsoleted by this RFC, with a more compact, more universal, better-performing syntax. | ||
===== Comparison with other languages ===== | ===== Comparison with other languages ===== | ||
Line 155: | Line 232: | ||
Hack has [[https:// | Hack has [[https:// | ||
- | That is atypical among languages with such functionality and introduces additional questions about what sigil to use and other implementation details. | + | That is atypical among languages with such functionality and introduces additional questions about what sigil to use and other implementation details. |
The Hack syntax was the subject of the [[https:// | The Hack syntax was the subject of the [[https:// | ||
Line 170: | Line 247: | ||
Could be interpreted as evaluating to " | Could be interpreted as evaluating to " | ||
+ | |||
+ | Haskell also has a ''&'' | ||
==== F# ==== | ==== F# ==== | ||
Line 191: | Line 270: | ||
A pipeline operator `|>` has been [[https:// | A pipeline operator `|>` has been [[https:// | ||
- | ===== Related RFCs ===== | + | ==== OCaml ==== |
- | This RFC is deliberately kept small and contained. | + | OCaml includes a [[https://riptutorial.com/ocaml/example/ |
- | + | ||
- | * [[https://wiki.php.net/rfc/partial_function_application|Generic partial application]]. An RFC for this already exists, but does not yet have an implementation. This approach would allow for folding almost any multi-parameter function into a single-parameter function, including offering a clean way to reference a single parameter function by name. | + | |
- | + | ||
- | For instance, the above examples could be rewritten as follows: | + | |
- | + | ||
- | <code php> | + | |
- | $holiday = " | + | |
- | $result = getCurrentUser() | + | |
- | |> getShoppingList(?, | + | |
- | |> mostExpensiveItem(?, | + | |
- | |> getPromotions(? | + | |
- | + | ||
- | ServerRequest:: | + | |
- | |> authenticate(? | + | |
- | |> $router-> | + | |
- | |> fn($request) => $request-> | + | |
- | |> renderResult(? | + | |
- | |> buildResponse(? | + | |
- | |> emit(?); | + | |
- | + | ||
- | $new_list = $list | + | |
- | |> itmap(fn($x) => $x*2) | + | |
- | |> itfilter(fn($x) => $x %2) | + | |
- | |> iterator_to_array(? | + | |
- | </ | + | |
- | + | ||
- | + | ||
- | * [[rfc: | + | |
- | + | ||
- | <code php> | + | |
- | function handle_request(RequestInterface $request) => $request | + | |
- | |> authenticate(? | + | |
- | |> $router-> | + | |
- | |> fn($request) => $request-> | + | |
- | |> renderResult(? | + | |
- | |> buildResponse(? | + | |
- | |> emit(?); | + | |
- | + | ||
- | handle_request(ServerRequest:: | + | |
- | </ | + | |
===== Future Scope ===== | ===== Future Scope ===== | ||
This RFC suggests a number of additional improvements. | This RFC suggests a number of additional improvements. | ||
+ | |||
+ | * Generic partial function application. | ||
* Iterable right-hand side. The pipe operator as presented here can only be used in a hard-coded fashion. | * Iterable right-hand side. The pipe operator as presented here can only be used in a hard-coded fashion. | ||
Line 244: | Line 285: | ||
These options are mentioned here for completeness and to give an indication of what is possible, but are *not* in scope and are *not* part of this RFC at this time. | These options are mentioned here for completeness and to give an indication of what is possible, but are *not* in scope and are *not* part of this RFC at this time. | ||
- | |||
===== Proposed PHP Version(s) ===== | ===== Proposed PHP Version(s) ===== | ||
Line 260: | Line 300: | ||
===== Patches and Tests ===== | ===== Patches and Tests ===== | ||
- | PR is available here: https:// | + | PR is available here: https:// |
(It's my first PHP PR. Please be gentle.) | (It's my first PHP PR. Please be gentle.) | ||
rfc/pipe-operator-v2.txt · Last modified: 2021/07/20 15:34 by crell