LEVIATHAN v962456e · 962456eee1

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 the expr page;
  • expr::Expr::<(User) => bool>(...) spells the function type F explicitly. This form exists only at compile time and carries no runtime payload;
  • a binds or args array 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::Expr position.
  • 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 enum cannot 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.
  • --expand shows 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));
not run — shows the output of the --expand compiler flag

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.