Metaprogramming
Where a lambda literal is reified
A lambda literal becomes an expr::Expr<F> in any position whose expected type is expr::Expr<F>: a call argument, a declared variable, a return value or a field initializer.
since 0.1.0-alpha.1linuxwindowswasm
Description
A lambda literal is reified whenever the type the context expects is expr::Expr<F>. The compiler
looks at the expected type, not at the lambda, so reification happens in every position that carries one:
- a call argument, where the parameter's type is
expr::Expr<F>; - a variable declaration (a local or a global) whose declared type is
expr::Expr<F>; - a
returnstatement in a function whose declared return type isexpr::Expr<F>; - a field initializer in a class, for a field whose declared type is
expr::Expr<F>.
The body must be exactly one expression: (u) => <expression>. A block body, even one that only
contains return, is rejected. Every lambda parameter may be the root of a Field path, so a lambda with
several parameters reifies too, with the caveat in the Rules below.
Only a lambda literal is converted. A variable that happens to hold a lambda cannot be turned into an
Expr after the fact, because the tree is built from the lambda's source at compile time.
The four positions
class User {
int age;
new User(int a) { age = a; }
}
string show(expr::Expr<(User) => bool> e) {
expr::Node t = e.tree;
match (t) {
expr::Bin => {
expr::Bin b = t;
return "${b.op}";
}
else => { return "other"; }
}
}
class Rules {
expr::Expr<(User) => bool> adult = (u) => u.age >= 18;
}
expr::Expr<(User) => bool> senior() {
return (u) => u.age >= 65;
}
console.writeln("argument: " + show((u) => u.age < 18));
expr::Expr<(User) => bool> declared = (u) => u.age == 30;
console.writeln("variable: " + show(declared));
console.writeln("return: " + show(senior()));
console.writeln("field: " + show(Rules().adult));
argument: <
variable: ==
return: >=
field: >=
Rules
- The lambda must be a literal written in the position. Passing a variable of a function type where an
expr::Expr<F>is expected is a compile error (only a lambda literal can be reified to expr::Expr<F>). - The body must be one expression, not a block.
- A lambda literal matches a plain function-typed parameter and an
expr::Expr<_>parameter equally well. Two overloads that differ only in that way make a call with a literal ambiguous, and that is a compile error rather than a silent preference. Give the overloads different names, or assign the lambda to a typed local first to choose one. - A
Fieldpath has no marker for which parameter it belongs to. In(a, b) => a.age < b.ageboth sides areField(["age"]). A consumer that must tell parameters apart should use single-parameter lambdas. - A lambda whose parameter type arrives through a generic parameter reifies like any other, so a generic
where(expr::Expr<(E) => bool> p)method can be called with a literal.
Examples
Multiple parameters and a generic method
class User {
int age;
new User(int a) { age = a; }
}
class Query<E> {
int count = 0;
Query<E> where(expr::Expr<(E) => bool> p) {
count = count + 1;
return this;
}
}
string side(expr::Node n) {
match (n) {
expr::Field => { return n.path.joinToString("."); }
else => { return "?"; }
}
}
expr::Expr<(User, User) => bool> younger = (a, b) => a.age < b.age;
expr::Node t = younger.tree;
match (t) {
expr::Bin => {
expr::Bin b = t;
console.writeln("${side(b.l)} ${b.op} ${side(b.r)}");
}
else => { console.writeln("other"); }
}
console.writeln(younger.fn(User(20), User(30)));
Query<User> q = Query();
q.where((u) => u.age > 1).where((u) => u.age < 90);
console.writeln(q.count);
age < age
true
2
class User { int age; }
string pick(((User) => bool) f) => "closure";
string pick(expr::Expr<(User) => bool> f) => "expr";
// error: ambiguous lambda argument: matches both a function parameter and an expr::Expr parameter;
// extract a typed local to select
console.writeln(pick((u) => u.age > 1));
See also
- Expression reification — lambdas as data — A lambda literal in a position typed expr::Expr<F> compiles to an ordinary closure plus a walkable tree of its checked body, which is what query builders translate to other languages.
- The reifiable subset — The expressions that may appear in a reified lambda and the tree node each one becomes.
- Reification errors — The compile errors a reified lambda can produce, what each means, and how to fix it.