Skip to content

Functions & Closures

Functions are first-class values in Achronyme. They can be assigned to variables, passed as arguments, and returned from other functions.

Define named functions with fn name(params) { body }.

fn add(a, b) {
return a + b
}
assert(add(3, 4) == 7)
fn greet() {
return "hello"
}
assert(greet() == "hello")

Create unnamed functions with fn(params) { body } and assign them to variables.

let double = fn(x) { x * 2 }
assert(double(5) == 10)
let mul = fn(a, b) { return a * b }
assert(mul(3, 7) == 21)

The last expression in a function body is its return value. Use return for early exits.

fn square(x) {
x * x
}
assert(square(4) == 16)
fn abs(x) {
if x >= 0 { return x }
return -x
}
assert(abs(-5) == 5)

Functions without a return and no trailing expression return nil.

Functions capture variables from their enclosing scope by reference. Changes to captured variables are visible to all closures that share them.

fn make_adder(n) {
return fn(x) { x + n }
}
let add5 = make_adder(5)
assert(add5(3) == 8)

Closures can capture and mutate state:

fn make_counter() {
mut count = 0
return fn() {
count = count + 1
return count
}
}
let counter = make_counter()
assert(counter() == 1)
assert(counter() == 2)
assert(counter() == 3)

Multiple closures can share the same mutable variable:

fn make_pair() {
mut val = 0
let getter = fn() { val }
let setter = fn(x) { val = x }
return [getter, setter]
}
let pair = make_pair()
let get = pair[0]
let set = pair[1]
assert(get() == 0)
set(42)
assert(get() == 42)

Functions can accept and return other functions.

fn apply(f, x) {
return f(x)
}
assert(apply(fn(x) { x * x }, 5) == 25)
fn compose(f, g) {
return fn(x) { f(g(x)) }
}
let inc = fn(x) { x + 1 }
let dbl = fn(x) { x * 2 }
let inc_then_dbl = compose(dbl, inc)
assert(inc_then_dbl(3) == 8)

Common patterns like map, filter, and reduce:

fn filter(arr, pred) {
mut result = []
for x in arr {
if pred(x) { push(result, x) }
}
return result
}
let evens = filter([1, 2, 3, 4, 5, 6], fn(x) { x % 2 == 0 })
assert(len(evens) == 3)
fn reduce(arr, init, f) {
mut acc = init
for x in arr { acc = f(acc, x) }
return acc
}
assert(reduce([1, 2, 3, 4, 5], 0, fn(a, b) { a + b }) == 15)

Named functions can call themselves. Use named function expressions (fn name(params) { ... }) when assigning a recursive function to a variable.

fn factorial(n) {
if n <= 1 { return 1 }
return n * factorial(n - 1)
}
assert(factorial(5) == 120)
let fib = fn fib(n) {
if n < 2 { return n }
return fib(n - 1) + fib(n - 2)
}
assert(fib(10) == 55)

Functions can include optional type annotations on parameters and return types:

fn add(a: Field, b: Field) -> Field {
return a + b
}
fn is_positive(x: Field) -> Bool {
return x > 0
}
let double = fn(x: Field) -> Field { x * 2 }

Mixed typed and untyped parameters are allowed — this is gradual typing:

fn scale(x: Field, factor) {
x * factor
}

In circuit mode, annotations are checked at compile time. See Type Annotations for details on type checking rules and constraint savings.

FeatureSyntaxNotes
Named functionfn name(a, b) { body }Hoisted in scope
Anonymous functionfn(a, b) { body }First-class value
Named function expressionlet f = fn f(n) { ... }Enables recursion via variable
Typed parametersfn f(x: Field, y: Bool)Optional, checked in circuits
Return typefn f(x) -> Field { body }Optional, checked in circuits
Implicit returnlast expression in bodyNo return needed
Explicit returnreturn exprEarly exit
Closure captureautomaticBy reference