Functions
Module-level declarations
Section titled “Module-level declarations”fn add( a: Int, b: Int,): Int { a + b}- The last expression in the body is the return value.
- Explicit
returnis for early exits only. - All module-level
fndeclarations are hoisted. Mutual recursion works. - Trailing commas are allowed in parameter lists and argument lists.
Return type inference
Section titled “Return type inference”The return type annotation is optional on private functions. When omitted, the compiler infers it from the body:
fn add(a: Int, b: Int) { a + b } // inferred: Intfn greet(name: String) { "Hello" } // inferred: Stringfn noop() { } // inferred: Voidpub fn must always have an explicit return type. This keeps module
boundaries explicit and self-documenting:
pub fn run(): Void { ... } // OK. Explicitpub fn bad() { ... } // Error E135: pub fn missing return typeIf two private functions with omitted return types reference each other (mutual recursion with both unannotated), the compiler emits E136 and requires at least one of them to carry an explicit annotation.
Named arguments at the call site
Section titled “Named arguments at the call site”let r = add(a: 1, b: 2) // namedlet r = add(1, 2) // positionalDefault parameter values
Section titled “Default parameter values”fn greet( name: String, prefix: String = "Hello",): String { "${prefix}, ${name}!"}Parameter destructuring
Section titled “Parameter destructuring”A parameter may be a pattern instead of a plain name. The pattern is bound
at the start of the function body, exactly like a let destructuring.
Variant / record destructuring:
type Point { Point(x: Double, y: Double) }
fn sumSquares(Point(x:, y:): Point): Double { x * x + y * y}Tuple destructuring:
fn addPair(#(a, b): #(Int, Int)): Int { a + b}Mixed plain and destructured parameters are allowed:
fn scale(n: Double, Point(x:, y:): Point): Double { let sum = x + y n * sum}Restrictions:
- Destructured parameters are positional only. They have no call-site label and cannot be passed by name.
- Default values (
= expr) are not allowed on destructured parameters. - Closures do not support parameter destructuring; use a
letbinding inside the body instead.
Closures / anonymous functions
Section titled “Closures / anonymous functions”let add5 = fn( x: Int,): Int { x + 5 }
fn make_adder( base: Int,): fn(Int): Int { fn( x: Int, ): Int { base + x }}Closures are first-class values. They capture their enclosing lexical environment.
Higher-order functions
Section titled “Higher-order functions”fn apply( f: fn(Int): Int, v: Int,): Int { f(v) }
fn compose( f: fn(Int): Int, g: fn(Int): Int,): fn(Int): Int { fn( x: Int, ): Int { f(g(x)) }}Function type aliases
Section titled “Function type aliases”Use type to give a name to a function type:
type IntOp = fn(Int): Inttype Predicate = fn(Int): Bool
fn apply_op(f: IntOp, x: Int): Int { f(x) }fn apply_pred(p: Predicate, x: Int): Bool { p(x) }Generic function type aliases are supported:
type Transform<A, B> = fn(A): B
fn apply(f: Transform<Int, String>, x: Int): String { f(x) }Aliases are transparent: fn(Int): Int and IntOp are interchangeable.
Function type aliases are compile-time only. They have no representation in generated Apex.
|> pipe operator
Section titled “|> pipe operator”Passes the left value as the first argument of the right-hand function:
debug 3 |> double // 6debug 3 |> double |> inc // 7debug 4 |> square |> double // 32Use _ as a placeholder to control where the piped value lands:
fn add(a: Int, b: Int): Int { a + b }fn mul(a: Int, b: Int): Int { a * b }
debug 2 |> add(_, 3) // 5 . Same as add(2, 3)debug 2 |> add(_, 3) |> mul(10, _) // 50. Mul(10, add(2, 3))
fn greet(greeting: String, name: String): String { greeting + ", " + name + "!"}debug "world" |> greet("Hello", _) // Hello, world!Without _, the piped value is prepended as the first argument. With _, the
piped value replaces each _ in the argument list and no implicit prepend occurs.
Generic functions
Section titled “Generic functions”fn identity<T>( x: T,): T { x }fn first<T>( items: List<T>,): Option<T> { ... }