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/06/07 01:12] – Update on the expectation of PFA passing. crell | rfc:pipe-operator-v2 [2021/07/04 01:11] – Add example and fix patch URL crell | ||
---|---|---|---|
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 != ' | ||
); | ); | ||
Line 72: | Line 72: | ||
The left-hand side of the pipe may be any value or expression. | The left-hand side of the pipe may be any value or expression. | ||
- | While any callable style may be used, in practice the [[rfc: | + | ===== Language theory ===== |
+ | The pipe operator is a form of function composition. | ||
- | <code php> | + | It also cleanly enables |
- | $result = "Hello World" | + | |
- | |> htmlentities(? | + | |
- | |> explode(? | + | |
- | |> array_map(strtoupper(? | + | |
- | |> array_filter(? | + | |
- | </ | + | |
- | And the example from the start of this RFC could be written as: | + | ===== 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 = " | + | |
- | $result = getCurrentUser() | + | |
- | |> getShoppingList(? | + | |
- | |> mostExpensiveItem(? | + | |
- | |> getPromotions(?, | + | |
- | </ | + | |
- | Functions that accept their first parameter by reference are supported, and will behave exactly as if they were called | + | Should a version of [[rfc: |
+ | The examples below largely assume that the first-class-callable RFC has passed, which as of this writing appears guaranteed. | ||
- | + | Additionally, | |
- | The pipe operator evaluates immediately. | + | |
- | + | ||
- | <code php> | + | |
- | $holiday = " | + | |
- | $new_function = fn($user) => $user | + | |
- | |> getShoppingList(?, | + | |
- | |> mostExpensiveItem(?, | + | |
- | |> getPromotions(?, | + | |
- | + | ||
- | $new_function(getCurrentUser()); | + | |
- | </ | + | |
- | + | ||
- | ===== More robust example with PSR-7 ===== | + | |
- | + | ||
- | With partial functions: | + | |
- | + | ||
- | <code php> | + | |
- | ServerRequest:: | + | |
- | |> authenticate(? | + | |
- | |> $router-> | + | |
- | |> fn($request) => $request-> | + | |
- | |> renderResult(? | + | |
- | |> buildResponse(? | + | |
- | |> emit(?); | + | |
- | </ | + | |
- | + | ||
- | Without partial functions: | + | |
- | + | ||
- | <code php> | + | |
- | ServerRequest:: | + | |
- | |> ' | + | |
- | |> [$router, ' | + | |
- | |> fn($request) => $request-> | + | |
- | |> ' | + | |
- | |> ' | + | |
- | |> ' | + | |
- | </ | + | |
===== Alternate comprehension syntax ==== | ===== Alternate comprehension syntax ==== | ||
Line 146: | Line 99: | ||
yield $c($val); | yield $c($val); | ||
} | } | ||
- | } | + | }; |
} | } | ||
Line 153: | 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: | + | And now comprehension-like behavior can be written using pipes, without the need for a dedicated syntax. |
- | $new_list = $list | + | |
- | |> itmap(fn($x) => $x*2) | + | <code php> |
- | |> itfilter(fn($x) => $x %2) | + | $list = [1, 2, 3, 4, 5]; |
- | |> iterator_to_array(?); | + | |
+ | $new_list = $list | ||
+ | |> itmap(fn($x) => $x * 2) | ||
+ | |> itfilter(fn($x) => $x % 3) | ||
+ | |> 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 176: | 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:// | ||
+ | * The [[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 200: | Line 248: | ||
Could be interpreted as evaluating to " | Could be interpreted as evaluating to " | ||
+ | |||
+ | Haskell also has a ''&'' | ||
==== F# ==== | ==== F# ==== | ||
Line 221: | Line 271: | ||
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]]. This RFC already exists, and will hopefully be approved | + | |
- | + | ||
- | * [[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 250: | Line 286: | ||
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 266: | Line 301: | ||
===== 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