Skip to content

Instantly share code, notes, and snippets.

@morrisonlevi
Last active February 2, 2018 09:19
Show Gist options
  • Save morrisonlevi/fa7984c04ff176b5a87c to your computer and use it in GitHub Desktop.
Save morrisonlevi/fa7984c04ff176b5a87c to your computer and use it in GitHub Desktop.
Using the syntax for ES6 arrow functions wouldn't work in PHP. Here's why and how we could work around it.

In EcmaScript 2015 (ES6) the expression (x) => x * 2 means to create an anonymous function with one parameter x that will return x * 2. For example:

(x) => x * 2
// is equivalent to:
function(x) { return x * 2; }

A modified example from documentation by Mozilla Developer Network page demonstrates how they are useful:

var a = [
    "Hydrogen",
    "Helium",
    "Lithium",
    "Beryl­lium"
];

var a2 = a.map(function(s){ return s.length }); // pre-ES6

var a3 = a.map((s) => s.length); // ES6

There has been some talk about how we can use arrow function expressions in PHP. In PHP using the same syntax would have some ambiguities:

// Does this mean:
//   1. Create an array key with the result of `($x)` and a value with `$x * 2`
//   2. Create an array with one value that is an anonymous function
[($x) => $x * 2]

// Does this mean:
//   1. Yield a key with the result of `($x)` and a value with `$x * 2`
//   2. Yield an anonymous function
yield ($x) => $x * 2;

This is why Bob Weinand proposed using ~> instead of =>. However, if we allow type declarations there is another issue. In the definition (Type &$x) => expr the (Type &$var) part can parse as "take constant Type and variable $var and do a bitwise and & operation." After that the => will be an unexpected token. Even though the rule would be invalid the parser doesn't know that far ahead it will error and it doesn't know which rule to pick. Changing the token from => to ~> doesn't affect this issue.

We could solve the first ambiguities with prefering the current meaning with key => value and requiring the meaning with closures to wrap them in (). We could solve the latter ambiguity with a backtracking parser since it will eventually error and then know to pick the other rule. However, I really think this is a bad idea.

So how can we have shorter closures without this mess? One simple way is to require the function prefix:

// clearly an array with an anonymous function
[function($x) => $x * 2];

// clearly yields an anonymous function
yield function($x) => $x * 2;

// clearly an anonymous function
function(Type &$x) => expr;

Requiring the function prefix mitigates one of the value parts of arrow functions: they are short.

Another option would be to resolve the ambiguities with keys and values but to change the type information in parameters:

(&$input: array) => expr

By putting the type after the variable (similar to how we declare return types) we no longer have the issues with mis-parsing. Of course, that's not how we declare parameter types currently. I think we would need to permit it everywhere and deprecate the current syntax with the type being prefixed. (By deprecate I mean in PHP 8 and not remove it until PHP 9 or later)

I would prefer that we shorten the function keyword to fn:

[fn($x) => $x * 2]

This preserves the shortness of the expression while providing unambiguous, simple parsing. Of course, now we have a similar issue: we have both fn and function.

What concerns do you have about fn($x) => $x * 2 or function($x) => $x * 2? I will be writing a proper RFC later but I wanted to get discussion going now.

@ssipos90
Copy link

ssipos90 commented Feb 2, 2018

if sharing declaration context is allowed:

$y = 3;
$fn = (int $x => $x + $y);

if not:

$y = 3;
$fn = (int $x => $x + $y) use ($y);

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment