Navigation

Expression Interpreter

This example implements a strict expression language with arithmetic, booleans, let bindings, lambdas, application, and recursive bindings. The evaluator emits effects for environment lookup, local scope, and failure; handlers decide how those requests run.

The same module also exports scalable expression generators used by the benchmark suite.

Expression syntax

Expressions are plain tagged Nix attrsets. Constructors keep the evaluator independent from any parser or source format.

nix
num = n: { _tag = "Expr"; _variant = "Num"; inherit n; };
add = l: r: { _tag = "Expr"; _variant = "Add"; inherit l r; };
lam = param: body: { _tag = "Expr"; _variant = "Lam"; inherit param body; };
app = fn: arg: { _tag = "Expr"; _variant = "App"; inherit fn arg; };

Handler boundary

Variable lookup, scoped execution, and failure are effects. Recursive bindings work by building a closure whose environment refers back to itself, while the handler owns the actual environment map.

nix
lookup = name: send "lookup" name;
getEnv = send "getEnv" null;
fail = msg: send "fail" msg;

run = expr:
  let result = handle {
    handlers = mkHandler { };
    state = { env = { }; };
  } (eval expr);
  in if result ? error then throw "eval error: ${result.error}" else result.value;

Benchmark generators

exprs.nix generates large recursive and nested expressions. The benchmark suite imports these generators from examples/interp.

nix
interp.exprs.benchmarks.fib10
interp.exprs.benchmarks.lets500
interp.exprs.benchmarks.sum1000