Skip to main content

13  Functions & Closures

Functions and closures in Edva are first-class values with uniform 16-byte representation and full static type inference.

13.1 Signatures, types, and runtime ABI

Aspect Syntax / Representation Rules & Diagnostics
Multi-arg signature f: Int, String => <> Declared on the line preceding f = a, b => body (E2009 if combined inline)
Zero-arg signature f: () => Int Defined with f = !=> body and invoked with postfix f!
Void return signature f: String => () Returns Unit (void in LLVM IR)
Nested function type #type Cb (Int => Int)
apply: (Int => Int), Int => Int
Wrap function types in parentheses (...) when inside records, containers, or parameter lists
Runtime representation { ptr fn_ptr, ptr env_ptr } (16 bytes) fn_ptr: machine code pointer; env_ptr: arena-allocated capture struct pointer (null for non-capturing functions). Zero RTTI or vtables

13.2 Function definition forms

Form Syntax Example Parameter Binding & Semantics Diagnostic
Explicit parameter fn add = a, b => a + b Bare comma-separated names before => (never parenthesize (a, b) =>) E2040: explicit _ =>
Nullary function tick = !=> count = count + 1 0-parameter function; called via tick! —
Grouped implicit lambda (_ * 2), (10 - _), ((a = _, b = 1)) Innermost (...) group with free _ becomes a 1-arg function —
Operator section (* 2), (+ "!"), (.<. 1) Space after operator required; RHS binds at RBP 130 (* 2 + 3 \(\equiv\) _ => _ * (2 + 3)) E2031: missing space (>3)
Field-accessor section (.name), (.0), (.a.b) Parenthesized leading dot projects field/index with a fresh internal parameter (never shadows outer _) E2064: bare (.)
Headless choice fn [< 0] "neg" [0] "zero" | "pos" Scrutinee-less guarded choice lowers to a 1-arg function over _ —
Headless guard fn ['a'...'z'], ![' ', '\n'] Scrutinee-less bracket guard lowers to a T => <> predicate —
#use io

#type Item (id: Int, label: String)

apply: (Int => Int), Int => Int
apply = f, x => f(x)

classify = [< 0] "neg" [0] "zero" | "pos"
get_label: Item => String
get_label = (.label)

io::out $ apply((* 3), 7)                  // 21
io::out $ apply((100 - _), 30)             // 70
io::out $ classify(-5)                     // neg
io::out $ get_label(Item(1, "sensor"))     // sensor

13.3 Closures, lexical capture, and returns

Feature Syntax / Mechanism Behavior
Lexical capture make_adder = base => (n => base + n) Captures outer bindings into an arena-allocated environment struct (env_ptr)
Tail return Final expression in body Evaluates as the function’s return value
Valued early return --| expr Immediately exits enclosing function with expr (E3084 on return type mismatch)
Void early return Bare --| Immediately exits a () => () void function
Ram return sugars c |->, c |+>, c |-->, c |++> When c is <+>, early-returns <->, <+>, <-->, or <++>
#use io

make_Downloads: Int => (Int => Int)
make_Downloads = factor =>
   x =>
      x < 0 | --| 0
      x * factor

scale5 = make_Downloads(5)
io::out $ (scale5(4), scale5(-2))   // (20, 0)

13.4 Invocation and pipeline operators

Form Syntax Precedence / Associativity Desugaring
Direct call f(a, b) Postfix (LBP 170, left) Direct invocation (no space before (, E2032)
Nullary call f! Postfix (LBP 170, left) Invokes 0-argument function f
Feed pipeline x $> f / x $> f(a) Feed (LBP 40, RBP 50, left) Prepends x as first argument: f(x) / f(x, a)
Infix apply f $ x / f(a) $ b Apply (LBP 30, RBP 30, right) Appends x / b as last argument: f(x) / f(a, b)
Block apply f $ + indented k = v block Apply (LBP 30, RBP 30, right) Passes indented key-value block as a single record argument
Monadic bind ram $>> cont Feed (LBP 40, RBP 50, left) Unwraps positive ram slot into ram-returning cont; short-circuits negative slot (E3091)