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
Lambda expressions are available from Rell 0.16.1.