Functions
Named functions
Section titled “Named functions”Define functions with the fn keyword.
fn greet() { print("Hello, RESONON!");}greet();
fn add(a, b) { return a + b;}print(add(3, 4)); // 7Early return
Section titled “Early return”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";}Implicit return
Section titled “Implicit return”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)); // 25Functions returning patterns
Section titled “Functions returning patterns”Functions can return patterns just like any other value.
fn c_major() { return [C4 E4 G4];}print(c_major());Anonymous functions
Section titled “Anonymous functions”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)); // trueClosures
Section titled “Closures”Anonymous functions capture their enclosing environment at creation time.
let multiplier = 10;let times_ten = fn(x) { return x * multiplier; };print(times_ten(5)); // 50Capture 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)Returning closures (factory pattern)
Section titled “Returning closures (factory pattern)”fn make_adder(n) { return fn(x) { return x + n; };}let add5 = make_adder(5);let add10 = make_adder(10);print(add5(3)); // 8print(add10(3)); // 13Nested closures
Section titled “Nested closures”fn make_linear(m) { return fn(b) { return fn(x) { return m * x + b; }; };}// Apply each returned function directly — calls chain left to right.print(make_linear(2)(3)(0)); // y = 2x + 3 at x=0 -> 3print(make_linear(2)(3)(5)); // 13
// Or bind an intermediate step and reuse it:let line = make_linear(2)(3); // y = 2x + 3print(line(0)); // 3print(line(5)); // 13Selective capture
Section titled “Selective capture”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 xlet use_y = fn() { return y; }; // captures only y
print(use_x()); // 10print(use_y()); // 20Closures as arguments
Section titled “Closures as arguments”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)); // 25This pattern is used extensively with array methods like .map() and .filter() — see Arrays and the Higher-order functions section below.
Name resolution in Resonon is deliberately simple: a function’s meaning depends only on its arguments and the current global state — never on where it happens to be called from. This is what makes select-and-execute reliable, where you run snippets in any order.
Three rules cover everything.
Named functions see globals, not their callers
Section titled “Named functions see globals, not their callers”A fn body resolves any free identifier — a name that is not one of its own parameters or locals — against the global scope (the top level of your script), regardless of who called it. A caller’s local variables can never leak into the functions it calls.
let tempo = 120;
fn beat_ms() { return 60000 / tempo; // `tempo` resolves to the global}
fn play(tempo) { // this parameter is local to `play` return beat_ms(); // `beat_ms` still uses the GLOBAL tempo, not this one}
print(beat_ms()); // 500print(play(999)); // 500 (not 60000 / 999)This keeps each function reasoning-local: to understand beat_ms, you only read its body and the globals it names.
Global lookups are live
Section titled “Global lookups are live”Because free identifiers resolve against the global scope at call time, redefining a global updates every function that reads it — you do not re-define the functions.
let tempo = 120;fn beat_ms() { return 60000 / tempo; }print(beat_ms()); // 500
tempo = 140;print(beat_ms()); // 428.57... (picks up the new value)This is the property live coding relies on: tweak a global, re-execute it, and your instrument functions follow along. Definition order does not matter either — a function may reference a global defined later in the file, as long as that global exists by the time the function is called.
Closures capture locals by value
Section titled “Closures capture locals by value”When you need a function to remember a local value — inside another function, or a loop — use a closure (an anonymous fn). It snapshots the variables it references at creation time (see Closures above), the deliberate opposite of a named function’s live global lookup.
fn make_adder(n) { return fn(x) { return x + n; }; // captures `n` by value, now}let add5 = make_adder(5);print(add5(3)); // 8Functions as values
Section titled “Functions as values”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)); // 12Higher-order functions
Section titled “Higher-order functions”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);// 15Recursion
Section titled “Recursion”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)); // 55Rest parameters
Section titled “Rest parameters”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)Spread arguments
Section titled “Spread arguments”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)); // 60Spread can be mixed with regular arguments:
let pair = #[2, 3];print(add3(1, ...pair)); // 6Round-trip with rest parameters
Section titled “Round-trip with rest parameters”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)); // 15Shared parameters
Section titled “Shared parameters”Containers (arrays, dicts, class instances) are shared when passed to functions. The function operates on the same data as the caller.
fn append_note(arr, note) { arr.push(note);}
let melody = #[C4, D4, E4];append_note(melody, F4);print(melody); // [C4, D4, E4, F4]To avoid modifying the original, pass a .clone():
append_note(melody.clone(), G4);print(melody); // [C4, D4, E4, F4] — unchangedType annotations
Section titled “Type annotations”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;}Union types
Section titled “Union types”Use | to allow multiple types:
fn accept(val: Number | String) -> Number | String { return val;}
fn to_number(input: Number | String | Boolean) -> Number { return input + 0;}Function types
Section titled “Function types”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)); // 10Doc comments
Section titled “Doc comments”Use /// to attach documentation to functions and variables.
/// Clamp a value to the given range./// Parameters:/// value - the value to clamp/// lo - lower bound/// hi - upper bound/// Returns: the clamped valuefn clamp(value: Number, lo: Number, hi: Number) -> Number { if value < lo { return lo; } if value > hi { return hi; } return value;}Multiple consecutive /// lines are joined together. show() displays the full typed signature alongside the doc comment, grouping it into description, parameters, and returns:
show(clamp);// Function: clamp(value: Number, lo: Number, hi: Number) -> Number//// Clamp a value to the given range.//// Parameters:// value: Number — the value to clamp// lo: Number — lower bound// hi: Number — upper bound//// Returns: the clamped valueDeclared parameter and return types are shown when present; functions written without annotations simply display their parameter names.
Private functions
Section titled “Private functions”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); // 11mylib.helper(5); // Error: 'helper' is privateprivate also works with let:
private let INTERNAL_CONSTANT = 42;Default parameter values
Section titled “Default parameter values”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)); // 10print(amplify(5, 3)); // 15Named arguments
Section titled “Named arguments”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 onlycreate_track("synth", 2, port: "USB"); // skip velocity, set portcreate_track("bass", 1, velocity: 80); // skip port, set velocityNext Steps
Section titled “Next Steps”Functions are Resonon’s smallest unit of reuse — next, bundle state and behavior together into objects.
- Classes & References — group fields and methods into instances
- Language Methods — the complete built-in method reference