Removing recursion via explicit callstack simulation
This technical deep dive explores a systematic, albeit verbose, method to eliminate recursion from TypeScript code by explicitly simulating the call stack. It addresses the common Node.js stack overflow problem, providing a practical, tested solution for developers facing deep recursion. The article is valuable on HN for its detailed technical explanation, performance analysis, and robust verification using property-based testing.
The Lowdown
The author presents a method for manually converting elegant recursive functions into gnarled imperative forms to achieve stack safety, particularly relevant in environments like Node.js where deep recursion can lead to stack overflows. This technique trades clarity and some performance for robustness by explicitly simulating the language's call stack.
- The post begins with simple examples, like summing a linked list, to illustrate cases where recursion can be easily replaced by iteration due to associativity and data structure simplicity.
- It then moves to binary tree summation, showing how branching data structures necessitate an explicit stack for iterative traversal, introducing the concept of "reifying execution."
- A complex, mutually-recursive
Tree<T>andForest<T>data structure is introduced to demonstrate a scenario where intuitive iterative conversion is difficult, thus necessitating a methodical approach. - The core technique involves defining explicit
StackFrametypes (e.g.,FoldTreeFrame,FoldForestFrame) that mirror a function's arguments, child calls, return values, and parent frames. - An iterative loop then processes these frames, simulating recursive calls by creating new frames and returns by updating a
returnValueand moving to theparentframe. Memory management, by nulling out child references, is also discussed. - The correctness of this verbose iterative solution is verified using property-based testing with
fast-check, comparing its output against the simpler recursive reference for a wide range of randomly generated inputs. - Performance benchmarks indicate that the iterative solution is approximately 2.2 times slower than its recursive counterpart, a trade-off deemed acceptable for ensuring stack safety.
- Limitations of the approach are discussed, specifically its inapplicability to polymorphically-recursive functions where stack frame types would dynamically change.
Ultimately, the article provides a detailed, systematic guide for transforming recursive algorithms into stack-safe iterative ones, emphasizing the crucial role of property-based testing in validating the correctness of such complex transformations for production-grade code.