In PHP anonymous functions and closures are really useful for callbacks but they are verbose:
array_map(function($x) {
return $x * $x * $x;
}, $input);
The short-closure proposal by Bob tries to address this by introducing ~>
:
array_map($x ~> $x * $x * $x, $input);
// or
array_map(($x) ~> $x * $x * $x, $input);
This shortens these types of expressions. However, there are concerns about ~>
:
- The tilde (
~
) is difficult to press in many keyboard layouts. - It's visually similar to
->
and=>
. - It doesn't allow you to specify types (e.g
(int $x) ~> $x * $y
). - They look weird when chaining.
- Some people didn't like omitting
()
with only one parameter.
And probably more. So I thought about ways we can avoid these issues and came up with an alternative proposal: use the expression => expr
to mean use(*) { return expr; }
and still require a function token.
The example from before becomes this:
array_map(function($x) => $x * $x * $x, $input);
This is still pretty short but I think it's less confusing and easier to parse (both by humans and parsers). Here is a direct comparison:
array_map(($x) ~> $x * $x * $x, $input);
array_map(function($x) => $x * $x * $x, $input);
Additionally it would allow us to add type declarations:
// live
array_map(function(int $x): int {
return $x * $x * $x;
}, $input);
// short-closures
array_map(function(int $x): int => $x * $x * $x, $input)
Bob's proposal doesn't allow it because the parameter list with references has conflicts. For example, in (Type &$x) => expr
the part (Type &$x)
parses as parenthesized constant & $variable
(meaning bitwise and the constant and variable). This proposal doesn't have such an issue because the function
prefix will make the parser take a different rule.
The expressions for this proposal also don't look so strange when chained compared to Bob's proposal:
$a ~> $b ~> $a * $b;
// alternatively
($a) ~> ($b) ~> $a * $b;
function($a) => function($b) => $a * $b;
As a bonus we could reuse these changes in other places like method definitions too:
class Foo {
private $property;
function getProperty() => $this->property;
}
It's certainly not as short as Bob's proposal – that is one drawback. However it is much easier to implement and we get full type declarations if we choose.
Lastly, here are some more examples:
namespace Lib;
// live
function sum(array $input) {
return array_reduce($input, function($a, $b) {
return $a + $b;
}, 0);
}
// short-closures
function sum(array $input) => array_reduce($input, function($a, $b) => $a + $b, 0);
// live
$this->onFulfilled(function() use($resolve) {
$resolve($this->result);
});
// short-closures
$this->onFulfilled(function() => $resolve($this->result));
// live
function sumEventScores($events, $scores) {
$types = array_map(
function($event) {
return $event['type'];
},
$events
);
return array_reduce(
$types,
function($sum, $type) use ($scores) {
return $sum + $scores[$type];
}
);
}
// short-closures:
function sumEventScores($events, $scores) {
$types = array_map(function ($event) => $event['type'], $events);
return array_reduce($types, function($sum, $type) => $sum + $scores[$type]);
}
// live
function reduce(callable $fn) {
return function($initial) use ($fn) {
return function($input) use ($fn, $initial) {
$accumulator = $initial;
foreach ($input as $value) {
$accumulator = $fn($accumulator, $value);
}
return $accumulator;
};
};
}
// short-closures
function reduce(callable $fn) => function($initial) => function($input) use($fn, $initial) {
$accumulator = $initial;
foreach ($input as $value) {
$accumulator = $fn($accumulator, $value);
}
return $accumulator;
};
It's a step forward but the "=>" operator is already pretty prevalent throughout PHP today.
I feel we need something different to make a clear distinction between lambdas and the array key/value assignment operator.
I'm curious where this is going and what Bob Weinand is gonna come up with, he already shared he's looking into a new proposal for the operator.