Skip to main content

Functions

Defining functions

fn add(a, b) {
return a + b
}
print(add(2, 3)) // 5

A function with no return returns nil. return with no value also returns nil.

Anonymous functions

Functions are first-class values:

var doubler = fn(n) {
return n * 2
}
print(doubler(21)) // 42

var square = fn(x) { return x * x }
print(square(5)) // 25

Arguments

Missing arguments default to nil; too many arguments is an error:

fn add(a, b) { return a + b }
print(add(1, 2, 3)) // error: too many arguments

Missing arguments default to nil:

fn greet(name, title) {
return title + " " + name
}
print(greet("Smith")) // nil Smith
print(greet("Smith", "Dr")) // Dr Smith

Functions as data

var ops = {
add: fn(a, b) { return a + b },
mul: fn(a, b) { return a * b },
}

print(ops.add(10, 3)) // 13 (dot notation for members)
print(ops.mul(4, 5)) // 20

Returning multiple values

A function returns a single value, but that value can be an array, and a destructuring declaration unpacks it into several variables in one step:

fn divmod(a, b) {
return [a / b, a % b]
}
var [c, e] = divmod(17, 5) // c is 3, e is 2
print(c, e) // 3 2

fn stats(list) {
var total = 0
for (v : list) { total += v }
return [total / len(list), total]
}
var [avg, sum] = stats([10, 20, 30, 40]) // avg 25, sum 100
print(avg, sum) // 25 100

Rules for unpacking an array:

var [a, b] = [1, 2] // a is 1, b is 2
var [first] = [9, 8] // extra elements are ignored (first is 9)
// var [x, y] = [1] // error: not enough elements

You cannot destructure an object

Destructuring works on arrays only. Returning an object and trying to unpack it as a pair is an error:

fn get_pair() {
return {this: "value"}
}
var [v, c] = get_pair()
// error: destructuring requires an array value, got object

To return named values, return an object and access it by key:

fn get_pair() {
return {v: 3, c: 5}
}
var pair = get_pair()
print(pair.v, pair.c) // 3 5

Each element of the pattern must be a plain name — there are no nested or ref patterns (var [{a, b}] = ... is not supported).

This is the idiomatic way to return "multiple values" — the function returns an array, the caller destructures it. The destructuring declares var / const / local bindings (but not ref, which cannot destructure), and it works on any array-valued expression, not just function calls.

Higher-order functions work naturally:

fn apply_twice(f, x) {
return f(f(x))
}
print(apply_twice(square, 2)) // 16

Recursion

Recursion depth is capped at 256 per execution context; exceeding it raises a clean STACK_OVERFLOW error.

fn fact(n) {
if (n <= 1) { return 1 }
return n * fact(n - 1)
}
print(fact(6)) // 720

Globals, not lexical closures

Functions read and write global variables directly, and there is no lexical capture of enclosing locals:

var counter = 0
fn increment() {
counter++
}
increment()
print(counter) // 1
fn broken() {
var inner = 42
return fn() { return inner } // error: inner is not visible here
}

This matters for callbacks and async tasks — reach data through globals or pass it as arguments.

Scope summary

  • Top-level var/const declarations are global.
  • var/const inside a function are local to that call.
  • Blocks (if/while/for/else, standalone {}) create new scopes; variables declared inside are local to that block.
  • For-loop init/condition/increment run in the enclosing scope.
  • There are no lexical closures — functions see only globals, not enclosing locals (see Globals, not lexical closures).