Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save morrisonlevi/5c3869df9fc438848ccf to your computer and use it in GitHub Desktop.
Save morrisonlevi/5c3869df9fc438848ccf to your computer and use it in GitHub Desktop.

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 ~>:

  1. The tilde (~) is difficult to press in many keyboard layouts.
  2. It's visually similar to -> and =>.
  3. It doesn't allow you to specify types (e.g (int $x) ~> $x * $y).
  4. They look weird when chaining.
  5. 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;
};
@chrisramakers
Copy link

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.

@Salagir
Copy link

Salagir commented Sep 23, 2015

I agree with this article, chrisramakers, and even more.
It seems to me we're not going to write short closure of just one line that much.
Thus, is there a real need to create a whole new syntax for that?

// live
usort($array, function($a, $b) { return $a->val <=> $b->val; });
// my suggestion (if you really want to rest your typing fingers)
usort($array, fn($a, $b) { rn $a->val <=> $b->val } );
// original suggestion
usort($array, ($a, $b) ~> $a->val <=> $b->val);

How much time will be needed to read php for a beginner?
With my suggestion he will understand what fn and rn most likely mean. But seeing the original suggestion, he'll be lost. And good luck searching for "~>" in the manual.

@bwoebi
Copy link

bwoebi commented Sep 25, 2015

@chrisramakers actually, I'm working together with Levi to have something we can agree on… hence this draft ;-)

@natanfelles
Copy link

Today I was wondering if there is a shorter syntax and I ended up finding this gist and the RFC.

I think ~> is very similar to -> and after => the code gets loose, difficult to understand which block belongs.

Proposal:

$id = {
   return 25;
};

$id = () {
   return 25;
};

$id = (User $user) {
   return $user->id;
};

$id = (User $user) use ($foo) {
   return $user->id . $foo;
};

$bar = $class->method('abc', {
    return 'baz';
});

$bar = $class->method('abc', (DB $db) {
    return $db->protectIdentifier('baz');
});

Braces are cognitively associated with functions, I think this would look good on short closures.

Optionally, and to define arguments, one can prefix parentheses with the arguments within.

In short:

  • The word function is not used.
  • Parentheses () for arguments are optional.
  • Braces {}are required to surround the function.

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