===== Introduction ===== It has been expressed both on and off the mailing list that [[rfc:arrow_functions|arrow functions]] are a highly desired feature. It seems that we mostly do not agree on //how// this feature should work. The purpose of this document and its corresponding vote is to help us come to consensus. The result of this vote is non-binding, meaning that voting here is not accepting something into the language. This is a way to express your preference of the two proposed syntaxes in voting form. The result of this vote will be used to draft a final RFC targeting PHP 7.2. ===== Concerns ===== Having reviewed the past discussions about arrow functions I have determined that there are a few main points that people disagree on. The ordering here has no significance; they are numbered only so it is easier to refer to a given point later. - How variables/values from the outer scope are bound. - How concise it should be / Readability. - Using a keyword or not. - Will it ever be able to support statement blocks instead of only a single expression? - Syntax even aside from the above concerns. ===== Binding variables from the outer scope ===== The current proposal binds variables used from the outer scope by value. To address the concern from the community I propose that by default we bind by value and allow opting into binding by reference. Being by-value as the default and opting into by-reference is prudent for two reasons: - It mimics how we deal with parameters and the return value. By default they are by-value and are changed to pass-by-reference and return-by-reference when the respective ampersands are present. The amended proposal makes this consistent. - Binding by-reference has a higher performance cost. The details of how to opt-in are detailed in [[#syntax]]. ===== Syntax ==== I am unsure if the other points listed in [[#concerns|Concerns]] are truly reconcilable. Instead I suggest two syntaxes for us to discuss and choose between. Each has different strengths and weaknesses with regards to points 2-5. Both options support type declarations for parameters and the return type, as well as parameter references and return by-reference. ==== Option 1: fn-prefixed ==== This is what current RFC outlines except it has the optional leading ampersand to indicate that bound variables from the outer scope should be done by-reference instead of by-value. The simplified grammar looks like this: T_FN '(' parameter_list ')' T_DOUBLE_ARROW expr This requires a new keyword ''fn'' and token ''T_FN''. There are a few usages in ''.phpt'' tests and I expect it will similarly disrupt test suites of other projects as well. ==== Option 2: curly-brace enclosed ==== The simplified grammar looks like this: '{' '(' parameter_list ')' T_DOUBLE_ARROW expr '}' You may omit the parenthesis around the parameter list when there is only a single variable and neither references nor type information is provided: {$x => $x + 2} To support this syntax the grammar is modified to allow these closures wherever expressions are allowed //except// as a statement because we allow freestanding blocks. This is an acceptable trade-off because such a closure would not have any positive observable effect. This syntax would restrict the ability to add object literals a-la JavaScript. Specifically it would prevent the ability to use arbitrary expressions as object literal keys. ===== Examples ===== Here are some snippets from real code-bases converted to use the syntaxes: $s1 = fn($c) => $callable($factory($c), $c); $s2 = {$c => $callable($factory($c), $c)}; $this->existingSchemaPaths = array_filter($paths, fn($v) => in_array($v, $names)); $this->existingSchemaPaths = array_filter($paths, {$v => in_array($v, $names)}); function complement_s1(callable $f) { return fn(...$args) => !$f(...$args); } function complement_s2(callable $f) { return {(...$args) => !$f(...$args)}; } function reject_s1($collection, callable $function) { return filter($collection, fn($value, $key) => !$function($value, $key)); } function reject_s2($collection, callable $function) { return filter($collection, {($value, $key) => !$function($value, $key)}); } // not real code but typical of such $result = Collection::from([1, 2]) ->map(fn($v) => $v * 2) ->reduce(fn($tmp, $v) => $tmp + $v, 0); $result = Collection::from([1, 2]) ->map({$v => $v * 2}) ->reduce({($tmp, $v) => $tmp + $v}, 0); I'll point out only one thing and leave the rest to the you to analyze: the closing curly brace ''}'' makes it a bit easier to distinguish the end of the expression when the closure is directly passed as a parameter. === Of references === As an example of all the places references can be used: &fn &(&$x) => function_that_takes_ref_and_returns_ref($x, $y) ^ ^ ^ | | \ Parameter is passed by reference | \ Function returns by reference \ Variables bound from the outer-scope are bound by reference Here is the same closure written in the brace-enclosed style: &{ &(&$x) => function_that_takes_ref_and_returns_ref($x, $y) } ^ ^ ^ | | \ Parameter is passed by reference | \ Function returns by reference \ Variables bound from the outer-scope are bound by reference [[https://chat.stackoverflow.com/users/4251625/wes-stark|Wes Stark]] suggests that we move the return-by-reference sigil to behind the parameters where we put return type information: {($x): & => function_that_returns_ref($x, $y) } ^ \ Function returns by reference This cleans up the potential density if you close by reference and return by reference but means that we have a break in convention. === Of Nested Closures === Nikita Popov requested that I show an example of nested closures: $add2 = fn($x) => fn($y) => $x + $y; $add2 = {$x => {$y => $x + $y}}; ===== Vote ===== * Prefixed with fn * Enclosed by curly-braces * Reuse function * No Preference / Other Preference * Prefixed with fn * Enclosed by curly-braces * Reuse function * No Preference / Other Preference