Functions & Closures
Function definitions, closures, and recursion.
Functions are first-class values in Achronyme. They can be assigned to variables, passed as arguments, and returned from other functions.
Function Declarations
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")
Anonymous Functions
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)
Return Values
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.
Closures
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)
Higher-Order Functions
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)
Recursion
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)
Type Annotations
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.
Quick Reference
| Feature | Syntax | Notes |
|---|---|---|
| Named function | fn name(a, b) { body } | Hoisted in scope |
| Anonymous function | fn(a, b) { body } | First-class value |
| Named function expression | let f = fn f(n) { ... } | Enables recursion via variable |
| Typed parameters | fn f(x: Field, y: Bool) | Optional, checked in circuits |
| Return type | fn f(x) -> Field { body } | Optional, checked in circuits |
| Implicit return | last expression in body | No return needed |
| Explicit return | return expr | Early exit |
| Closure capture | automatic | By reference |