LEVIATHAN v962456e · 962456eee1

Metaprogramming

siteId — identifying a lambda in the source

Every reified lambda literal gets a number that is fixed at compile time, distinct for each lambda in the source, and the same on every run.

since 0.1.0-alpha.1linuxwindowswasm

Description

Expr.siteId is an int that identifies the lambda literal the Expr was built from, not the moment it was built. The compiler assigns the numbers while it checks the program: the first reified lambda gets 0, the next 1, and so on, in the order the lambdas appear in the source file.

Because the number belongs to the place in the source, a lambda inside a function that is called many times has the same siteId on every call. Two different lambdas, even with identical text, have different ids. The number is fixed at compile time. It involves no hash of the source, no clock and no randomness, so the same program compiled again gets the same ids.

A library can use siteId as a cache key: a query builder that translates a lambda to SQL can remember the translation under its siteId and skip the work the next time the same lambda is reached. The language does not cache anything itself. Keeping such a cache is the consumer's job.

One id per lambda in the source

class User {
    int age;
    new User(int a) { age = a; }
}

expr::Expr<(User) => bool> first = (u) => u.age > 1;
expr::Expr<(User) => bool> second = (u) => u.age > 1;

expr::Expr<(User) => bool> make() {
    return (u) => u.age > 1;
}

expr::Expr<(User) => bool> third = make();
expr::Expr<(User) => bool> fourth = make();

console.writeln(first.siteId);
console.writeln(second.siteId);
console.writeln(first.siteId != second.siteId);
console.writeln(third.siteId == fourth.siteId);
0
1
true
true

Rules

  • A siteId is unique among the reified lambdas of one compiled program and starts at 0.
  • Evaluating the same lambda literal again, for example by calling the function that contains it again, produces an Expr with the same siteId.
  • The numbers follow source order, but a program should treat them as opaque identifiers and compare them only for equality.

Examples

A cache keyed by siteId

class User {
    int age;
    new User(int a) { age = a; }
}

Map<int, string> cache = Map();
int translations = 0;

string translate(expr::Expr<(User) => bool> e) {
    if (cache.has(e.siteId)) {
        return cache.at(e.siteId);
    }
    translations = translations + 1;
    string sql = "translation ${translations}";
    cache[e.siteId] = sql;
    return sql;
}

void lookup() {
    console.writeln(translate((u) => u.age > 18));
}

lookup();
lookup();
console.writeln(translate((u) => u.age > 21));
translation 1
translation 1
translation 2

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.
  • expr — The expression-reification tree: runtime, walkable descriptions of lambda bodies.