Closures and tail recursion
Tail self-recursion runs in constant stack (guaranteed)
Section titled “Tail self-recursion runs in constant stack (guaranteed)”A self-call is in tail position when it is the function’s whole result, with nothing left to do to it. When a function returns such a call, the compiler guarantees the call does not grow the stack. So a tail-recursive function runs in constant stack and will not overflow, however deep the recursion:
count = (n :: Num, acc :: Num) -> Num => n == 0 ? acc : count(n - 1, acc + n) ~ the self-call IS the `:` branch → tail positionTail position flows through the constructs that yield a value directly: ?/| match
arms, if/ternary branches, the tail of a < > block, and a |> pipeline. A self-call
not in tail position stays ordinary recursion (e.g. n * fact(n - 1), whose result
is multiplied first). So does a tail call to a different function — general/mutual tail
calls are a later follow-up. There is no surface syntax for it: nothing is written to ask
for the guarantee.
(See examples/tail_recursion.qn, which recurses 1,000,000 deep.)
Closures — capture by = (value) vs := (reference)
Section titled “Closures — capture by = (value) vs := (reference)”A function written inside another function’s body is a closure: it can read the enclosing locals it refers to. How each name is captured is decided by the operator that bound it — no capture list, no marker, mirroring the mutability rule for variables and records:
=captures by value — a frozen snapshot taken when the closure is created;:=captures by reference — one shared mutable cell. Writes through it, from inside the closure or outside, are visible to everyone sharing it, and the cell survives the frame that created it.
^ = () -> Num => < total := 0 ~ `:=` -> captured BY REFERENCE bump = (n :: Num) => < total := total + n ~ writes the SHARED cell; the effect persists across calls total > bump(10) ~ total -> 10 bump(20) ~ total -> 30 (same cell)
base = 7 ~ `=` -> captured BY VALUE (a frozen copy) addBase = (x :: Num) => x + base
total + addBase(5) ~ 30 + 12 = 42>A non-capturing nested function may recurse (fact = (n :: Num) => … fact(n-1) …).
Nested closures may capture from any enclosing frame — the shared := cell is threaded
through every level. A closure value may itself be captured by another closure and called.
A closure may also be passed to a function whose parameter has the matching
function type and called there.
Closures are monomorphic: parameters and captured values are concrete-typed. Capturing a
polymorphic value, generic closures, and returning a closure across frames are deferred
— see Known limitations. (See examples/closures.qn.)