rfc:pipe-operator
Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revisionLast revisionBoth sides next revision | ||
rfc:pipe-operator [2016/07/21 00:08] – Semicolons... pollita | rfc:pipe-operator [2017/09/06 03:33] – Definition fixes and examples haskellcamargo | ||
---|---|---|---|
Line 1: | Line 1: | ||
====== PHP RFC: Pipe Operator ====== | ====== PHP RFC: Pipe Operator ====== | ||
- | * Version: 0.2 | + | * Version: 0.3 |
* Date: 2016-04-29 | * Date: 2016-04-29 | ||
- | * Author: Sara Golemon < | + | * Author: Sara Golemon < |
* Status: Under Discussion | * Status: Under Discussion | ||
* First Published at: http:// | * First Published at: http:// | ||
Line 8: | Line 8: | ||
===== Introduction ===== | ===== Introduction ===== | ||
- | A common PHP OOP pattern is the use of method chaining, or what is also known as " | + | A common PHP OOP pattern is the use of method chaining, or what is also known as " |
For example, the following shows a SQL query expression built out of component pieces, then executed: | For example, the following shows a SQL query expression built out of component pieces, then executed: | ||
- | | + | <code php> |
- | -> | + | $rs = $db |
- | -> | + | -> |
- | -> | + | -> |
- | -> | + | -> |
- | -> | + | -> |
+ | -> | ||
+ | </ | ||
This works well enough for OOP classes which were designed for fluent calling, however it is impossible, or at least unnecessarily arduous, to adapt non-fluent classes to this usage style, harder still for functional interfaces. | This works well enough for OOP classes which were designed for fluent calling, however it is impossible, or at least unnecessarily arduous, to adapt non-fluent classes to this usage style, harder still for functional interfaces. | ||
Line 23: | Line 25: | ||
While decomposing these expressions to make use of multiple variables is an option, this can lead to reduced readability, | While decomposing these expressions to make use of multiple variables is an option, this can lead to reduced readability, | ||
- | | + | <code php> |
- | $dic = buildDic($config); | + | $config = loadConfig(); |
- | $app = getApp($dic); | + | $dic = buildDic($config); |
- | $router = getRouter($app); | + | $app = getApp($dic); |
- | $dispatcher = getDispatcher($router, | + | $router = getRouter($app); |
- | $logic = dispatchBusinessLogic($dispatcher, | + | $dispatcher = getDispatcher($router, |
- | $render = renderResponse($logic); | + | $logic = dispatchBusinessLogic($dispatcher, |
- | $psr7 = buildPsr7Response($render); | + | $render = renderResponse($logic); |
- | $response =emit($psr7); | + | $psr7 = buildPsr7Response($render); |
+ | $response = emit($psr7); | ||
+ | </ | ||
Or: | Or: | ||
- | | + | <code php> |
- | $x = buildDic($x); | + | $x = loadConfig(); |
- | $x = getApp($x); | + | $x = buildDic($x); |
- | $x = getRouter($x); | + | $x = getApp($x); |
- | $x = getDispatcher($x, | + | $x = getRouter($x); |
- | $x = dispatchBusinessLogic($x, | + | $x = getDispatcher($x, |
- | $x = renderResponse($x); | + | $x = dispatchBusinessLogic($x, |
- | $x = buildPsr7Response($x); | + | $x = renderResponse($x); |
- | $response =emit($x); | + | $x = buildPsr7Response($x); |
+ | $response = emit($x); | ||
+ | </ | ||
- | These sorts of chains could also, conceivably, | + | This may lead to error prone code, enforcing reassigment or pollution of the scope for readability. |
- | | + | <code php> |
- | $response = emit(buildPsr7Response(renderResponse(dispatchBusinessLogic($dispatcher, | + | $dispatcher = getDispatcher(getRouter(getApp(buildDic(loadConfig()))), |
+ | $response = emit(buildPsr7Response(renderResponse(dispatchBusinessLogic($dispatcher, | ||
+ | </ | ||
- | This RFC aims to improve code readability by bringing fluent expressions to functional and OOP libraries not originally designed for the task. | + | This RFC aims to improve code readability by bringing fluent expressions to functional and OOP libraries not originally designed for the task. Several languages already provide support for the pipe operator or, at least, for definining it. Some of the languages that offer support are Elixir, F#, LiveScript and HackLang. |
===== Proposal ===== | ===== Proposal ===== | ||
- | Introduce the "Pipe Operator" | + | Introduce the "Pipe Operator" |
- | This feature | + | This feature |
==== PSR7 Example ==== | ==== PSR7 Example ==== | ||
Line 62: | Line 70: | ||
Here's the equivalent chain of function calls as demonstrated in the intro section above: | Here's the equivalent chain of function calls as demonstrated in the intro section above: | ||
- | | + | <code php> |
- | | + | $response = loadConfig() |
- | | + | |> buildDic($$) |
- | | + | |> getApp($$) |
- | | + | |> getRouter($$) |
- | | + | |> getDispatcher($$, |
- | | + | |> dispatchBusinessLogic($$, |
- | | + | |> renderResponse($$) |
- | | + | |> buildPsr7Response($$) |
+ | |> emit($$); | ||
+ | </ | ||
- | ==== File collection | + | ==== File Collection |
- | As an example, consider the following real block of code I wrote while creating a test importer (to migrate HHVM format tests into PHPT format). | + | As an example, consider the following real block of code I wrote while creating a test importer (to migrate HHVM format tests into PHPT format). Please try not to get hung up into whether or not it's " |
- | | + | <code php> |
- | array_merge( | + | $ret = |
- | $ret, | + | array_merge( |
- | getFileArg( | + | $ret, |
- | array_map( | + | getFileArg( |
- | function ($x) use ($arg) { return $arg . '/' | + | array_map( |
- | array_filter( | + | function ($x) use ($arg) { return $arg . '/' |
- | scandir($arg), | + | array_filter( |
- | function ($x) { return $x !== ' | + | scandir($arg), |
- | ) | + | function ($x) { return $x !== ' |
) | ) | ||
) | ) | ||
- | ); | + | |
+ | | ||
+ | </ | ||
This block of code is readable, but one must carefully examine the nesting to determine what the initial input it, and what order it traverses the steps involved. | This block of code is readable, but one must carefully examine the nesting to determine what the initial input it, and what order it traverses the steps involved. | ||
- | With this proposal, the above could be rewritten as: | + | With this proposal, the above could be easily |
- | | + | <code php> |
- | |> array_filter($$, | + | $ret = scandir($arg) |
- | |> array_map(function ($x) use ($arg) { return $arg . '/' | + | |> array_filter($$, |
- | |> getFileArg($$) | + | |> array_map(function ($x) use ($arg) { return $arg . '/' |
- | |> array_merge($ret, | + | |> getFileArg($$) |
+ | |> array_merge($ret, | ||
+ | </ | ||
- | This clearly, and unambiguously shows `scandir()` as the initial source of data, that it goes through an `array_filter` to avoid recursion, an `array_map` to requalify the paths, some local function, and finally a merge to combine the result with a collector variable. | + | This, cleary |
==== FBShipIt Example ==== | ==== FBShipIt Example ==== | ||
Line 107: | Line 120: | ||
Also consider [[https:// | Also consider [[https:// | ||
- | | + | <code php> |
- | |> self:: | + | return $changeset |
- | |> self:: | + | |> self:: |
- | $$, | + | |> self:: |
- | $config[' | + | $$, |
- | ) | + | $config[' |
- | |> self:: | + | ) |
- | |> self:: | + | |> self:: |
- | |> self:: | + | |> self:: |
- | |> self:: | + | |> self:: |
- | |> ShipItUserFilters:: | + | |> self:: |
- | $$, | + | |> ShipItUserFilters:: |
- | FBToGitHubUserInfo:: | + | $$, |
- | ) | + | FBToGitHubUserInfo:: |
- | |> self:: | + | ) |
- | $$, | + | |> self:: |
- | $config[' | + | $$, |
- | ?? self:: | + | $config[' |
- | ) | + | ?? self:: |
- | |> self:: | + | ) |
- | |> self:: | + | |> self:: |
- | |> self:: | + | |> self:: |
+ | |> self:: | ||
+ | </ | ||
This presents every step taken by the common filter chain in an easy to follow list of actions. | This presents every step taken by the common filter chain in an easy to follow list of actions. | ||
Line 136: | Line 151: | ||
While most ambiguities of `$$` between pipe replacement variable and variable variables are covered in the lexer rule, the following case is not accounted for: | While most ambiguities of `$$` between pipe replacement variable and variable variables are covered in the lexer rule, the following case is not accounted for: | ||
- | | + | <code php> |
- | $b = ' | + | $a = 1; |
- | var_dump($$ /* comment */ {' | + | $b = ' |
- | // Expected: int(1) | + | var_dump($$ /* comment */ {' |
- | // Actual: Use of $$ outside of a pipe expression | + | // Expected: int(1) |
+ | // Actual: Use of $$ outside of a pipe expression | ||
+ | </ | ||
This particular quirk of the parser (allowing comments in the middle of a variable-variable-brace-expression) is doubtlessly a rare occurrence in the wild, so the current implementation stopped short of trying to resolve it. | This particular quirk of the parser (allowing comments in the middle of a variable-variable-brace-expression) is doubtlessly a rare occurrence in the wild, so the current implementation stopped short of trying to resolve it. | ||
Potential resolutions: | Potential resolutions: | ||
- | * Use a less-ambiguous token. | + | * Use a less-ambiguous token. `$>`, which mirrors `|>`, is my personal favorite. Downshot: doesn' |
- | * Get very creative in the parser. | + | * Get very creative in the parser. Since ' |
- | Note that HHVM does not handle this case either. | + | Note that HHVM does not handle this case either. Nor, in fact, does it handle mere whitespace between `$$` and `{expr}`, which the attached PHP implementation does. |
- | **Update:** HackLang is normally supposed to disallow variable-variables, | + | **Update:** HackLang is normally supposed to disallow variable-variables, |
===== Proposed PHP Version(s) ===== | ===== Proposed PHP Version(s) ===== | ||
Line 159: | Line 176: | ||
===== Future Scope ===== | ===== Future Scope ===== | ||
- | The current proposal limits use of the `$$` to a single replacement per expression. | + | The current proposal limits use of the `$$` to a single replacement per expression. This feature could potentially be expanded to allow multiple uses of `$$` within a single RHS expression. |
===== Third-party Arguments ===== | ===== Third-party Arguments ===== | ||
Line 171: | Line 188: | ||
==== In favor ==== | ==== In favor ==== | ||
- | * Produces cleaner, more readable code | + | * Produces cleaner, more readable code, in order the things are executed |
* Doesn' | * Doesn' | ||
+ | * Enforces immutability and data transformation, | ||
==== Against ==== | ==== Against ==== | ||
Line 178: | Line 196: | ||
* The new tokens are inobvious and difficult to google for | * The new tokens are inobvious and difficult to google for | ||
* Pipe chaining in other languages follows different rules \\ (e.g. implicit first arg, rather than explicit placeholder) | * Pipe chaining in other languages follows different rules \\ (e.g. implicit first arg, rather than explicit placeholder) | ||
- | * Potentially | + | * Potentially |
* No opportunity for error catching/ | * No opportunity for error catching/ | ||
* Can be implemented using intermediate variables | * Can be implemented using intermediate variables | ||
===== Proposed Voting Choices ===== | ===== Proposed Voting Choices ===== | ||
- | Adopt the PIpe Operator yes/ | + | Adopt the Pipe Operator yes/ |
===== Patches and Tests ===== | ===== Patches and Tests ===== |
rfc/pipe-operator.txt · Last modified: 2017/09/22 13:28 by 127.0.0.1