Introducing Achronyme — a language for zero-knowledge proofs. Read the announcement

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

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
Navigation