Functions are values
Closures power callbacks, event handlers and modules in JavaScript, so understanding them from the inside pays off well beyond Chiya.
This is the lesson where Chiya becomes genuinely powerful: functions that build functions.
Evaluating fn(a, b) { a + b } doesn't run anything — it makes a function value. That value remembers three things: its parameter names, its body, and the environment it was created in. Remembering the environment is what makes a closure: a function that can still use outer variables after the code that defined them has finished.
let adder = fn(a) { fn(b) { a + b } };
let add2 = adder(2);
add2(3) // 5 — add2 still remembers a = 2Calling a function
A call evaluates the callee and the arguments, checks the callee really is a function and that the number of arguments matches, then creates a new Env whose outer is the function's saved environment — not the caller's. Parameters are defined in that new env, the body is evaluated in it, and a ReturnValue is unwrapped.
function call(callee, args) {
if (callee?.kind !== "fn") throw new Error(`Not a function: ${typeOf(callee)}`);
if (args.length !== callee.params.length)
throw new Error(`Expected ${callee.params.length} arguments but got ${args.length}`);
const scope = new Env(callee.env);
callee.params.forEach((p, i) => scope.define(p, args[i]));
const result = evaluate(callee.body, scope);
return result instanceof ReturnValue ? result.value : result;
}Because the new scope's outer is the creation environment, Chiya has lexical scope, like JavaScript: what a function can see depends on where it's written, not where it's called.