Lambdas: Design

A lambda expression is denoted by using the lambda arrow ==>. Left of the arrow are arguments to the anonymous function and on the right hand side is either an expression or a list of statements enclosed in curly braces {}.

<?hh

namespace Hack\UserDocumentation\Lambdas\Examples\Design\Introduction;

class User {
  public string $name;
  protected function __construct(string $name) { $this->name = $name; }
  static function get(int $id): User {
    // Load user from database, return a stub for the example
    return new User("User" . strval($id));
  }
}

function getUsersFromIds(Vector<int> $userids): Vector<User> {
  return $userids->map($id ==> User::get($id));
}

function run(): void {
  var_dump(getUsersFromIds(Vector { 1, 2, 3, 4 }));
}

run();
Output
object(HH\Vector)#3 (4) {
  [0]=>
  object(Hack\UserDocumentation\Lambdas\Examples\Design\Introduction\User)#4 (1) {
    ["name"]=>
    string(5) "User1"
  }
  [1]=>
  object(Hack\UserDocumentation\Lambdas\Examples\Design\Introduction\User)#5 (1) {
    ["name"]=>
    string(5) "User2"
  }
  [2]=>
  object(Hack\UserDocumentation\Lambdas\Examples\Design\Introduction\User)#6 (1) {
    ["name"]=>
    string(5) "User3"
  }
  [3]=>
  object(Hack\UserDocumentation\Lambdas\Examples\Design\Introduction\User)#7 (1) {
    ["name"]=>
    string(5) "User4"
  }
}

Annotating lambdas

Lambdas are equivalent to Closures, but you cannot type annotate them as a callable. However, you can:

  • annotate a passed or returned lambda call as (function(parametertypes): returntype) to provide type information.
  • annotate the lambada itself, both the arguments and the lambda return type.

The more type information you have, the more errors you can catch early.

<?hh

namespace Hack\UserDocumentation\Lambdas\Examples\Design\Annotation;

function getLambda(): (function(?int): bool) {
  return $x ==> $x === null || $x === 0;
}

function annotateLambda(string $s1, string $s2): array<string> {
  $strs = array($s1, $s2);
  usort(
    $strs,
    (string $s1, string $s2): int ==> strlen($s1) - strlen($s2)
  );
  return $strs;
}
function run(): void {
  var_dump(getLambda());
  var_dump(annotateLambda('Joel', 'Tim'));
}

run();
Output
object(Closure$Hack\UserDocumentation\Lambdas\Examples\Design\Annotation\Hack\UserDocumentation\Lambdas\Examples\Design\Annotation\getLambda;1186593164)#1 (1) {
  ["parameter"]=>
  array(1) {
    ["$x"]=>
    string(10) "<required>"
  }
}
array(2) {
  [0]=>
  string(3) "Tim"
  [1]=>
  string(4) "Joel"
}

Note that the definition of a function like Vector::filter is:

 public function filter ( (function(Tv): bool) $callback ): Vector<Tv>

That way you can only pass a lambda that returns bool. Hack infers the types and will print an error if you don't pass a valid closure. Therefore you don't have to annotate lambdas in many cases.

Parenthesis Around Arguments

Most of the time you need parenthesis around the arguments. However, if there is only one argument without a type annotation, with no default value, and your lambda has no return type annotation, you may omit the parenthesis. See examples for more information about this.

Precedence

The ==> operator has low precedence compared with other operators. This is convenient because it allows lambdas to have a complex body without the need of parenthesis. Furthermore, the operator is right associative and can be chained.

<?hh

namespace Hack\UserDocumentation\Lambdas\Examples\Design\Chained;

function chainedLambdas(): void {
  $lambda = $x ==> $y ==> $x - $y;
  $f = $lambda(4); // You are providing $x the value 4
  echo $f(2); // You are providing $y the value 2; thus this prints 2
}

function run(): void {
  chainedLambdas();
}

run();
Output
2