Skip to main content

Lambda expressions

A lambda expression is an anonymous function written with arrow syntax (->). It evaluates to a first-class function value, so you can store it in a variable, pass it to a function, return it from a function, and call it like any other function value.

x -> x * 2 // one parameter
(x, y) -> x + y // parenthesized parameters
() -> 42 // no parameters

Lambda body​

The body is either a single expression, or a value block whose trailing expression is the result:

val f: (integer) -> integer = n -> {
val doubled = n * 2;
doubled + 1
};
f(5); // 11

return isn't allowed inside a lambda; the result is the final expression. For the general rules of value blocks, see Value block arms.

Parameter types​

A parameter's type comes from either an explicit annotation or the expected function type at the use site (target typing). Annotations use the parenthesized form and are all-or-nothing: annotate every parameter or none. The return type is always inferred from the body.

val f: (integer) -> integer = x -> x * 2; // x is integer, from the declared type
f(10); // 20

val g = (x: integer) -> x * 2; // x is integer, from the annotation; g is (integer) -> integer
g(10); // 20

function reduce(xs: list<integer>, f: (integer) -> integer): integer {
var s = 0;
for (x in xs) s += f(x);
return s;
}

reduce([1, 2, 3], x -> x * x); // 14

A lambda whose parameters are neither annotated nor typed by an expected function type is a compile error. A parameterless lambda needs neither, because it has nothing to type.

Lambdas and standard library functions​

Lambdas fit the standard library functions that take a function value. For example, join_to_text() maps each element through a function before joining it; combined with a named argument, a one-line lambda supplies that function without spelling out the intervening optional parameters:

[1, 2, 3].join_to_text(transform = x -> "n" + x); // "n1, n2, n3"

When the work needs several statements, a lambda with a value-block body replaces the partial application that was previously the only option:

function checked_div(a: integer, b: integer): integer {
require(b != 0, 'division by zero');
return a / b;
}

val result = try_call(() -> {
val h = checked_div(20, 4);
h * h
}); // integer? - 25, or null if the call threw
print(result); // 25

Variable capture​

A lambda captures each enclosing local variable it refers to by reading the binding's current value when the lambda is created. Reassigning that outer variable afterwards isn't visible inside the lambda. This is a value capture in the same sense as any Rell assignment, not a deep snapshot: a captured mutable object (a list, map, or set) is captured by its reference, so an in-place mutation made to it after the lambda is created is visible inside the lambda. A captured variable is read-only inside the lambda.

var n = 7;
val g: (integer) -> integer = x -> x + n;
n = 100;
g(1); // 8 - reassigning n is not seen; g read n's value (7) when it was created

val xs = [1, 2, 3];
val h: () -> integer = () -> xs.size();
xs.add(4);
h(); // 4 - xs is captured by reference, so the in-place add IS seen
note

Lambda expressions are available from Rell 0.16.1.