Source: src/hql/transpiler/optimize/tco-optimizer.ts, src/hql/transpiler/optimize/mutual-tco-optimizer.ts, src/hql/transpiler/optimize/tail-position-analyzer.ts
HQL automatically optimizes tail-recursive functions into loops at transpile time, preventing stack overflow for deep recursion.
recur)__hql_trampoline_gen)// Write natural recursive code
(fn factorial [n acc]
(if (<= n 1)
acc
(factorial (- n 1) (* n acc))))
// Transpiles to efficient loop
// function factorial(n, acc) {
// while (true) {
// if (n <= 1) return acc;
// [n, acc] = [n - 1, n * acc];
// }
// }
(factorial 100 1) // Works without stack overflow
A function call is in tail position when it's the last operation before returning. Nothing happens after the recursive call - the result is returned directly.
(fn factorial [n acc]
(if (<= n 1)
acc
(factorial (- n 1) (* n acc)))) // Last operation - TAIL CALL
(fn factorial [n]
(if (<= n 1)
1
(* n (factorial (- n 1))))) // Must multiply AFTER recursive call
// NOT a tail call
Convert non-tail to tail by adding an accumulator parameter:
// Non-tail (stack grows)
(fn sum [n]
(if (<= n 0)
0
(+ n (sum (- n 1)))))
// Tail (constant stack)
(fn sum [n acc]
(if (<= n 0)
acc
(sum (- n 1) (+ acc n))))
(fn gcd [a b]
(if (=== b 0)
a
(gcd b (% a b))))
(fn fib [n a b]
(if (=== n 0)
a
(fib (- n 1) b (+ a b))))
(fib 50 0 1) // Fast, no stack overflow
HQL auto-detects self-recursive tail calls in fn declarations and transforms them into while(true) loops with destructuring parameter reassignment. No special form is required.
// HQL - write normal recursion
(fn factorial [n acc]
(if (<= n 1)
acc
(factorial (- n 1) (* n acc)))) // auto-optimized to while loop
HQL optimizes mutually recursive functions using Tarjan's SCC algorithm and a trampoline pattern:
(fn is-even [n]
(if (=== n 0) true
(is-odd (- n 1))))
(fn is-odd [n]
(if (=== n 0) false
(is-even (- n 1))))
(is-even 10000) // => true (no stack overflow)
The transpiler:
fn declarations() => otherFn(args))__hql_trampoline() to drive executionThree-or-more function cycles are also supported:
(fn step-a [n]
(if (=== n 0) "done-a" (step-b (- n 1))))
(fn step-b [n]
(if (=== n 0) "done-b" (step-c (- n 1))))
(fn step-c [n]
(if (=== n 0) "done-c" (step-a (- n 1))))
(step-a 9999) // => "done-a" (no stack overflow)
Mutually recursive generator functions (fn*) use a tagged-thunk pattern with __hql_trampoline_gen instead of the plain thunk used for sync functions. The yield* delegate calls in tail position are detected and transformed. This feature is implemented (transformGenToThunks in mutual-tco-optimizer.ts) but currently has no test coverage.
TCO only applies to:
fn declarations (not anonymous functions or lambdas)fn declarations (transformed to trampoline)NOT optimized:
await naturally breaks the call stack)try/catch/finally blocks