===== 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