Skip to content

Functions

Define functions with the fn keyword.

fn greet() {
PRINT "Hello, RESONON!";
}
greet();
fn add(a, b) {
return a + b;
}
PRINT add(3, 4); // 7

Use return to exit a function early.

fn absolute(x) {
if x < 0 { return -x; }
return x;
}
fn grade(score) {
if score >= 90 { return "A"; }
if score >= 80 { return "B"; }
if score >= 70 { return "C"; }
return "F";
}

The last expression in a function body is its return value when no explicit return is used.

fn square(x) {
x * x
}
PRINT square(5); // 25

Functions can return patterns just like any other value.

fn c_major() {
return [C4 E4 G4];
}
PRINT c_major();

Assign a function literal to a variable.

let double = fn(x) { return x * 2; };
PRINT double(7); // 14
let is_even = fn(n) { return n % 2 == 0; };
PRINT is_even(4); // true

Anonymous functions capture their enclosing environment at creation time.

let multiplier = 10;
let times_ten = fn(x) { return x * multiplier; };
PRINT times_ten(5); // 50

Capture is by value — changing the outer variable after creation does not affect the closure:

let base = 100;
let get_base = fn() { return base; };
base = 999;
PRINT get_base(); // 100 (still the original value)
fn make_adder(n) {
return fn(x) { return x + n; };
}
let add5 = make_adder(5);
let add10 = make_adder(10);
PRINT add5(3); // 8
PRINT add10(3); // 13
fn make_linear(m) {
return fn(b) {
return fn(x) { return m * x + b; };
};
}
let line = make_linear(2)(3); // y = 2x + 3
PRINT line(0); // 3
PRINT line(5); // 13

Closures only capture variables they actually reference, not the entire enclosing scope.

let x = 10;
let y = 20;
let use_x = fn() { return x; }; // captures only x
let use_y = fn() { return y; }; // captures only y
PRINT use_x(); // 10
PRINT use_y(); // 20

Anonymous functions can be passed directly as arguments without assigning them to a variable first.

fn apply(func, value) {
return func(value);
}
PRINT apply(fn(x) { return x * x; }, 5); // 25

This pattern is used extensively with array methods like .map() and .filter() — see Arrays and the Higher-order functions section below.

Functions are first-class values — they can be passed as arguments and returned from other functions.

fn apply_twice(func, x) {
return func(func(x));
}
let double = fn(x) { return x * 2; };
PRINT apply_twice(double, 3); // 12

Arrays provide .map(), .filter(), and .reduce() for functional-style processing. See Arrays for details.

let data = #[1, 2, 3, 4, 5];
data.map(fn(x) { return x * 2; });
// [2, 4, 6, 8, 10]
data.filter(fn(x) { return x % 2 == 0; });
// [2, 4]
data.reduce(fn(acc, x) { return acc + x; }, 0);
// 15

Functions can call themselves.

fn factorial(n) {
if n <= 1 { return 1; }
return n * factorial(n - 1);
}
PRINT factorial(5); // 120
fn fibonacci(n) {
if n <= 1 { return n; }
return fibonacci(n - 1) + fibonacci(n - 2);
}
PRINT fibonacci(10); // 55

Use ...name as the last parameter to collect any remaining arguments into an array.

fn log(level, ...messages) {
PRINT level;
for msg in messages {
PRINT " " + msg;
}
}
log("INFO", "server started", "port 8080");
// INFO
// server started
// port 8080
log("WARN");
// WARN (messages is an empty array)

Use ...array in a function call to unpack an array into individual arguments.

fn add3(a, b, c) {
return a + b + c;
}
let nums = #[10, 20, 30];
PRINT add3(...nums); // 60

Spread can be mixed with regular arguments:

let pair = #[2, 3];
PRINT add3(1, ...pair); // 6

Rest parameters and spread arguments pair naturally:

fn sum(...nums) {
return nums.reduce(fn(acc, x) { return acc + x; }, 0);
}
let values = #[1, 2, 3, 4, 5];
PRINT sum(...values); // 15

Use &name to accept a parameter by reference, allowing the function to modify the original value.

fn append_note(&arr, note) {
arr.push(note);
}
let melody = #[C4, D4, E4];
append_note(&melody, F4);
PRINT melody; // [C4, D4, E4, F4]

Parameters and return values can be annotated with types.

fn add(a: Number, b: Number) -> Number {
return a + b;
}
fn greet(name: String) -> String {
return "Hello, " + name;
}

Higher-order functions can annotate their function parameters:

fn apply(f: (Number) -> Number, x: Number) -> Number {
return f(x);
}
PRINT apply(fn(n) { return n * 2; }, 5); // 10

Use /// to attach documentation to functions and variables.

/// Clamp a value to the given range.
/// Returns lo if value < lo, hi if value > hi.
fn clamp(value, lo, hi) {
if value < lo { return lo; }
if value > hi { return hi; }
return value;
}

Multiple consecutive /// lines are joined together. Doc comments are visible via the show() introspection function:

show(clamp);
// Function: clamp(value, lo, hi)
// "Clamp a value to the given range.\nReturns lo if value < lo, hi if value > hi."

Mark functions and variables as private to prevent them from being exported by a module.

private fn helper(x) {
return x * 2;
}
fn public_api(x) {
return helper(x) + 1;
}

When this file is loaded via use, only public_api is accessible — helper is hidden.

use "mylib";
mylib.public_api(5); // 11
mylib.helper(5); // Error: 'helper' is private

private also works with let:

private let INTERNAL_CONSTANT = 42;

Parameters can have default values using = expr. When a caller omits those arguments, the defaults are used instead.

fn greet(name, greeting = "Hello") {
return greeting + ", " + name + "!";
}
PRINT greet("Alice", "Hi"); // Hi, Alice!
PRINT greet("Bob"); // Hello, Bob!

Defaults are evaluated at call time in the function scope, so they can reference earlier parameters:

fn range_step(start, end, step = 1) {
let result = Array();
let i = start;
loop {
if i >= end { break; }
result.push(i);
i = i + step;
}
return result;
}
PRINT range_step(0, 10, 2); // [0, 2, 4, 6, 8]
PRINT range_step(0, 5); // [0, 1, 2, 3, 4]

Defaults work with closures too:

let amplify = fn(x, factor = 2) { return x * factor; };
PRINT amplify(5); // 10
PRINT amplify(5, 3); // 15

Pass arguments by name at the call site using name: value syntax. This lets you skip optional parameters and provide arguments in any order.

fn point(x = 0, y = 0, z = 0) {
PRINT f"({x}, {y}, {z})";
}
point(1, 2, 3); // (1, 2, 3)
point(z: 5); // (0, 0, 5)
point(y: 2, x: 1); // (1, 2, 0)

Named arguments can be mixed with positional arguments — positional arguments must come first:

fn create_track(name, channel, velocity = 100, port = NUL) {
PRINT f"{name} ch={channel} vel={velocity} port={port}";
}
create_track("drums", 10); // positional only
create_track("synth", 2, port: "USB"); // skip velocity, set port
create_track("bass", 1, velocity: 80); // skip port, set velocity