Functions & closures

A function is declared with func <name> = define(<params>) { ... } - func and define are two separate keywords either side of the =, not one combined keyword.

func add = define(a, b) {
    return a + b;
}

output(add(2, 3)); // 5

Functions are first-class values: you can store one in a variable, pass it as an argument, or return it, exactly like any other value - a func declaration is really sugar for let <name> = define(...) {...};.

Default parameter values

Any parameter can have a default, using the same = syntax as a variable declaration. Once one parameter has a default, every parameter after it must too - this is enforced at parse time.

func greet = define(name, greeting = "Hello") {
    return greeting + ", " + name + "!";
}
output(greet("Ada"));       // "Hello, Ada!"
output(greet("Ada", "Hi")); // "Hi, Ada!"

func bad = define(a = 1, b) { return a + b; }
// parse error: Cannot have non default parameter 'b' after default parameters

Calls are strictly checked

Falx does not support variadic parameters or "extra arguments are ignored" - every call must supply exactly as many arguments as the function has parameters that lack a default (up to the total parameter count if you provide the optional ones too). Calling with the wrong count is a runtime error, not a silent truncation:

func add = define(a, b) { return a + b; }
output(add(1));       // runtime error: Expected 2 arguments, but got 1
output(add(1, 2, 3)); // runtime error: Expected 2 arguments, but got 3

Closures

A func declared inside another function closes over that function's local scope, including variables declared with let. Each call to the outer function creates a fresh, independent scope:

func makeCounter = define() {
    let count = 0;

    func increment = define() {
        count = count + 1; // mutates the ENCLOSING `count`, no special syntax needed
        return count;
    }

    return increment;
}

let c1 = makeCounter();
let c2 = makeCounter();
output(c1()); // 1
output(c1()); // 2 - c1's own `count` kept its state
output(c2()); // 1 - c2 has an entirely separate `count`

This is the same pattern used for private state in the closures tutorial and the bank-account example under Examples - a closure is Falx's stand-in for "private" fields on an object, alongside actual structs.

Recursion

Functions can call themselves normally. Recursion depth is genuinely tracked and enforced, not just approximated - going past 500 nested calls raises a runtime RecursionDepthExceededException ("Maximum recursion depth of 500 exceeded"), verified by actually recursing past it. Moderate recursion (tens to a few hundred levels) works fine, as in the example programs.

Returning nothing

return; with no value, or falling off the end of a function body, both produce null.