Skip to content
kata / a language workbench

Functions and closures

Define a function with func. Call it with positional arguments in parentheses.

func add(a: Int, b: Int): Int {
ret a + b
}
print(add(20, 22))

Parameter and return annotations are optional. When present, they are checked at runtime. A call with the wrong number of arguments is an error.

ret expression leaves the function immediately, including from a nested loop or conditional. A final expression also becomes the function’s result:

func square(n: Int): Int { n * n }
print(square(7))

A body that ends without a value-producing expression returns nil. Use explicit ret when it makes a longer function’s exit points easier to see.

Named functions can refer to functions defined later in the same scope, which supports mutual recursion. Execute the definitions before calling into that cycle. Recursive calls use the host stack, so use bounded loops for large computations.

let twice = func(n: Int): Int { ret n * 2 }
func apply(f: Func, value: Int): Int {
ret f(value)
}
print(apply(twice, 21))

Anonymous functions use the same parameter and body syntax, without a name after func. Func identifies a function value; it is not a parameterized function-signature type.

A closure sees the lexical bindings surrounding its definition.

func make_counter(): Func {
let count = 0
ret func(): Int {
count = count + 1
ret count
}
}
let next = make_counter()
print(next()) # 1
print(next()) # 2

The returned function keeps access to count. Each call to make_counter creates a separate binding, so counters from separate calls have independent scalar state.

Reassignment is visible through a captured binding. Shadowing with another let creates a different binding; an existing closure keeps the one it captured.

The counter tutorial develops this model with two independent counters. These examples capture integers. Resource-owning captures and generic cleanup have unresolved lifetime limits.