Metaprogramming
Reification in --expand output
How the compiler's --expand view shows a reified lambda as an explicit expr::Expr construction, and what it guarantees about the checked tree.
since 0.1.0-alpha.1linuxwindowswasm
Description
leviathan --expand file.lev prints the program after all compile-time rewriting, as source that can be
compiled again. For a reified lambda this shows exactly what the compiler built: an explicit construction
of an expr::Expr with the closure, the tree, the binds array and the site number.
--expand runs the full type checker before it prints. A program with a type error therefore fails
--expand exactly as it fails an ordinary compile, and what is printed is the checked tree: a captured
member chain is already one Bind, an enum member is already its integer, and a call has its resolved
name. The separate flag --ast-after-rules prints the tree before type checking, and is the one to use on
a program that does not type-check.
The printed construction is ordinary source:
- the closure is the lambda as written, with its parentheses normalized;
- the tree is built from the
expr::constructors described on theexprpage; expr::Expr::<(User) => bool>(...)spells the function typeFexplicitly. This form exists only at compile time and carries no runtime payload;- a
bindsorargsarray that is not empty is written as a function that builds the array element by element and is called at once; an empty array is written[]; - the last argument is the site number.
Rules
- The expanded text of a reified lambda is compiled by a later run without being reified a second time:
the printed lambda is passed to the constructor's plain function parameter, which is not an
expr::Exprposition. - For an ordinary program the expanded text compiles and behaves like the original, and expanding it again prints the same text.
- A program that declares an
enumcannot be compiled again from its expanded text, because the expansion of an enum contains helper names with a$, which the parser does not accept outside a quasiquote. --expandshows what the compiler built. It cannot show anything the checker rejected.
Examples
// program:
class User {
bool active;
new User(bool a) { active = a; }
}
expr::Expr<(User) => bool> plain = (u) => u.active;
int limit = 3;
expr::Expr<(User) => bool> pick = (u) => u.active && limit > 1;
// leviathan --expand prints (the class declaration is omitted here):
expr::Expr<(User) => bool> plain = expr::Expr::<(User) => bool>((u) => u.active, expr::Field(["active"]), [], 0);
int limit = 3;
expr::Expr<(User) => bool> pick = expr::Expr::<(User) => bool>((u) => (u.active && (limit > 1)), expr::Bin("&&", expr::Field(["active"]), expr::Bin(">", expr::Bind(0), expr::Lit(1))), (() => { Array<string | int | float | bool | None> __lvReifyArr = []; (__lvReifyArr = __lvReifyArr.add(limit)); return __lvReifyArr; })(), 1);
console.writeln((plain.siteId + pick.siteId));
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.
- Captured values and the binds array — How a reified lambda records the outside values it uses, once and in order, in the binds array, and what that guarantees.
- expr — The expression-reification tree: runtime, walkable descriptions of lambda bodies.