rfc:pipe-operator
Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
rfc:pipe-operator [2016/04/29 20:32] – Update with note from HackLang pollita | rfc:pipe-operator [2017/09/22 13:28] (current) – external edit 127.0.0.1 | ||
---|---|---|---|
Line 1: | Line 1: | ||
====== PHP RFC: Pipe Operator ====== | ====== PHP RFC: Pipe Operator ====== | ||
- | * Version: 0.1 | + | * 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 ===== | ||
- | Complex nested expressions can become progressively difficult to read the deeper they go with function returns leading | + | 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: | ||
+ | |||
+ | <code php> | ||
+ | $rs = $db | ||
+ | -> | ||
+ | -> | ||
+ | -> | ||
+ | -> | ||
+ | -> | ||
+ | </ | ||
+ | |||
+ | This works well enough for OOP classes | ||
+ | |||
+ | While decomposing these expressions to make use of multiple variables is an option, this can lead to reduced readability, | ||
+ | |||
+ | <code php> | ||
+ | $config = loadConfig(); | ||
+ | $dic = buildDic($config); | ||
+ | $app = getApp($dic); | ||
+ | $router = getRouter($app); | ||
+ | $dispatcher = getDispatcher($router, | ||
+ | $logic = dispatchBusinessLogic($dispatcher, | ||
+ | $render = renderResponse($logic); | ||
+ | $psr7 = buildPsr7Response($render); | ||
+ | $response = emit($psr7); | ||
+ | </ | ||
+ | |||
+ | Or: | ||
+ | |||
+ | <code php> | ||
+ | $x = loadConfig(); | ||
+ | $x = buildDic($x); | ||
+ | $x = getApp($x); | ||
+ | $x = getRouter($x); | ||
+ | $x = getDispatcher($x, | ||
+ | $x = dispatchBusinessLogic($x, | ||
+ | $x = renderResponse($x); | ||
+ | $x = buildPsr7Response($x); | ||
+ | $response = emit($x); | ||
+ | </ | ||
+ | |||
+ | This may lead to error prone code, enforcing reassigment or pollution of the scope for readability. These sorts of chains could also, conceivably, | ||
+ | |||
+ | <code php> | ||
+ | $dispatcher = getDispatcher(getRouter(getApp(buildDic(loadConfig()))), | ||
+ | $response = emit(buildPsr7Response(renderResponse(dispatchBusinessLogic($dispatcher, | ||
+ | </ | ||
+ | |||
+ | This RFC aims to improve code readability by bringing fluent | ||
===== Proposal ===== | ===== Proposal ===== | ||
- | Introduce the "Pipe Operator" | + | Introduce the "Pipe Operator" |
- | This feature | + | This feature |
- | 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): | + | ==== PSR7 Example ==== |
- | | + | Here's the equivalent chain of function calls as demonstrated in the intro section above: |
- | array_merge( | + | |
- | $ret, | + | <code php> |
- | getFileArg( | + | $response = loadConfig() |
- | array_map( | + | |> buildDic($$) |
- | function ($x) { return $arg . '/' | + | |> getApp($$) |
- | array_filter( | + | |> getRouter($$) |
- | scandir($arg), | + | |> getDispatcher($$, |
- | function ($x) { return $x !== ' | + | |> dispatchBusinessLogic($$, |
- | ) | + | |> renderResponse($$) |
+ | |> buildPsr7Response($$) | ||
+ | |> emit($$); | ||
+ | </ | ||
+ | |||
+ | ==== File Collection Example ==== | ||
+ | |||
+ | 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> | ||
+ | $ret = | ||
+ | array_merge( | ||
+ | $ret, | ||
+ | getFileArg( | ||
+ | array_map( | ||
+ | function ($x) use ($arg) { return $arg . '/' | ||
+ | array_filter( | ||
+ | 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) { 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 ==== | ||
+ | |||
+ | Also consider [[https:// | ||
+ | |||
+ | <code php> | ||
+ | return $changeset | ||
+ | |> self:: | ||
+ | |> self:: | ||
+ | $$, | ||
+ | $config[' | ||
+ | ) | ||
+ | |> self:: | ||
+ | |> self:: | ||
+ | |> self:: | ||
+ | |> self:: | ||
+ | |> ShipItUserFilters:: | ||
+ | $$, | ||
+ | FBToGitHubUserInfo:: | ||
+ | ) | ||
+ | |> self:: | ||
+ | $$, | ||
+ | $config[' | ||
+ | ?? self:: | ||
+ | ) | ||
+ | |> self:: | ||
+ | |> self:: | ||
+ | |> self:: | ||
+ | </ | ||
+ | |||
+ | This presents every step taken by the common filter chain in an easy to follow list of actions. | ||
===== Backward Incompatible Changes ===== | ===== Backward Incompatible Changes ===== | ||
Line 49: | 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 occurance | + | This particular quirk of the parser (allowing comments in the middle of a variable-variable-brace-expression) is doubtlessly a rare occurrence |
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) ===== | ||
- | 7.Next | + | 7.2 |
===== Open Issues ===== | ===== Open Issues ===== | ||
Line 72: | 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 ===== | ||
+ | |||
+ | Informal Twitter poll (821 respondents) results: https:// | ||
+ | |||
+ | * 62% "Love It" | ||
+ | * 24% " | ||
+ | * 14% "Hate It" | ||
+ | |||
+ | ==== In favor ==== | ||
+ | |||
+ | * Produces cleaner, more readable code, in order the things are executed | ||
+ | * Doesn' | ||
+ | * Enforces immutability and data transformation, | ||
+ | |||
+ | ==== Against ==== | ||
+ | |||
+ | * 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) | ||
+ | * Potentially confusing with variable-variables | ||
+ | * No opportunity for error catching/ | ||
+ | * 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.1461961958.txt.gz · Last modified: 2017/09/22 13:28 (external edit)