Language

Functions

Functions are first-class values — you can assign them to variables, pass them as arguments, and return them from other functions.

Named functions

The most common way to define a function. Return type and parameter types are optional:

fn int add(int a, int b) {
    return a + b
}

fn void greet(string name) {
    out "Hello, {name}!"
}

fn bool isAdult(int age) {
    return age >= 18
}

out add(3, 7)         // → 10
out isAdult(16)       // → false
greet("Sergio")       // → Hello, Sergio!

Arrow functions

Arrow functions are great for short callbacks. The return type goes before the parentheses:

let double = int (int n) => {
    return n * 2
}

let shout = string (string s) => {
    return s.toUpperCase() + "!"
}

out double(5)       // → 10
out shout("hello")  // → HELLO!

Anonymous functions

Functions without a name — useful when you need to pass logic around:

let run = fn void () {
    out "running..."
}

run()   // → running...

Default parameters

Parameters can have default values. If the caller doesn't pass the argument, the default is used. Default parameters must come after required ones:

fn string greet(string name = "World") {
    return "Hello, {name}!"
}

out greet()           // → Hello, World!
out greet("Sergio")   // → Hello, Sergio!

fn int add(int a, int b = 10) {
    return a + b
}

out add(5)      // → 15  (b defaults to 10)
out add(5, 3)   // → 8   (b supplied)

Recursion

Functions can call themselves. The call stack is tracked and shown clearly if an error occurs:

fn int factorial(int n) {
    if (n <= 1) { return 1 }
    return n * factorial(n - 1)
}

out factorial(6)   // → 720

fn int fibonacci(int n) {
    if (n <= 1) { return n }
    return fibonacci(n - 1) + fibonacci(n - 2)
}

out fibonacci(10)   // → 55

Functions as values

Functions can be stored in variables and passed as arguments:

fn int double(int n) { return n * 2 }
fn int square(int n) { return n * n }

let op = double   // store a function
out op(5)         // → 10

op = square       // swap it out
out op(5)         // → 25

// Pass as argument
fn int applyTwice(fn int op, int x) {
    return op(op(x))
}

out applyTwice(double, 3)   // → 12  (double(double(3)) = double(6) = 12)

Closures

Functions capture variables from the scope they're defined in:

let multiplier = 3
let nums = [1, 2, 3, 4]

// The lambda captures 'multiplier' from outside
let tripled = nums.map(x => x * multiplier)
out tripled   // → [3, 6, 9, 12]

// Making a counter with closure
fn make_counter() {
    let count = 0
    return fn void () {
        count = count + 1
        out count
    }
}

let counter = make_counter()
counter()   // → 1
counter()   // → 2
counter()   // → 3

Lambda syntax (for callbacks)

When passing functions to map, filter, reduce, or similar, use the short lambda syntax:

let nums = [1, 2, 3, 4, 5]

// Single parameter — no parentheses needed
nums.map(x => x * 2)

// Two parameters (value + index)
nums.map((x, i) => "{i}: {x}")

// Multi-line lambda
nums.filter(x => {
    let isEven = x % 2 == 0
    return isEven
})

// Reduce — accumulator + current value
nums.reduce(0, (acc, x) => acc + x)   // → 15