OJaml / docsProject documentation

Reference

First-Class Functions and Closures

Look up syntax, contracts, layouts, algorithms, and exact behavior.

OJaml supports first-class functions through heap-allocated closures and WebAssembly function tables. A top-level function can be called directly when statically known, or wrapped as a closure when passed as a value. Anonymous and local functions become pending lambdas with captured variables recorded from free-variable analysis. A local recursive function stores its own closure pointer in the captured environment when the function body refers to its name. The function-table ABI is generated from the program, so closure calls are not capped at a small fixed arity; returned and staged closures use the same generated call path.

closure pointer p

p + 0   table index
p + 4   captured value 0
p + 8   captured value 1
...

indirect call:
  call_indirect(type fn_n)
    env = p
    arg_1 ... arg_n
    table_index = load(p + 0)
Closure layout. Every closure is both an environment pointer and the source of its callable table entry.
let make_adder x =
  fun y -> x + y

let main =
  let add10 = make_adder 10 in
  add10 32
Closure capture. The anonymous function captures x. At runtime add10 is a closure whose environment stores 10.
c=(itable,  v0,…,vn)c = (i_{table},\;v_0,\ldots,v_n)
Closure value. The heap object stores the target function table index and all captured runtime values.

A closure stores a table index followed by captured values. Indirect calls load the function table index from the closure pointer and pass the closure pointer as an environment parameter. Lambda bodies recover captured values by loading from fixed offsets in that environment.

let main =
  let xs = List.cons 3 (List.cons 2 (List.cons 1 (List.empty ()))) in
  List.fold_left (fun acc x -> acc + x) 0 xs
Higher-order collection call. The fold callback is compiled as a closure and called indirectly by the List.fold_left runtime helper.
captures⁡(fun  x→e)=FV(e)∖{x}\operatorname{captures}(\texttt{fun}\;x\rightarrow e)=FV(e)\setminus\{x\}
Free-variable capture. Anonymous functions capture exactly the free variables that are local or already captured in the surrounding context.

This representation keeps direct calls efficient while supporting higher-order collection functions such as map, filter, exists, for_all, iter, and fold_left. The compiler emits the arity-specific WebAssembly function types required by the current program, and the standard library calls callbacks through call_indirect with the matching type.

  • Top-level functions receive closure wrappers when they are used as values.
  • Pending lambdas are emitted after top-level declarations, with stable function-table indices.
  • Indirect-call arities are generated from the current program, so closure calls scale with the highest first-class or staged function arity the source actually uses.
  • Captured locals and captured captures are both loaded into the new closure environment.