// trampoline : (thunk -> thunk | value) -> value // Runs a chain of thunks iteratively so recursion doesn't grow the JS call stack. // - Start with a "thunk" (a function with no args) // - While the result is still a function, keep calling it // - When a non-function value is produced, return it const trampoline = (thunk) => { let t = thunk; while (typeof t === "function") t = t(); // keep "bouncing" return t; // final value }; // foldl : (f, acc, xs, k) -> ... // // Fold an array xs from left to right in a continuation-passing style (CPS) and // use trampoline to stay stack-safe. // // Type-ish comments: // - f : (x, acc, k2) => ... // where k2 : (newAcc) => ... is a continuation that tells foldl what to do next // IMPORTANT: f is expected to call k2(newAcc) rather than directly returning newAcc. // - k : (finalAcc) => ... is the final continuation invoked at the end. // // Returns: a call that eventually triggers k(finalAcc). const foldl = (f, acc, xs, k) => { // step(i, accNow) describes "the rest of the fold" starting at index i // and current accumulator accNow. // // It returns either: // - a thunk (function) that when called computes the next step, or // - in the base case, a thunk that calls k(accNow). const step = (i, accNow) => { // Base case: we've processed every element in xs. if (i >= xs.length) return () => k(accNow); // Process the next element. const x = xs[i]; // We don't call f(x, accNow, ...) immediately on the call stack. // Instead, we return a thunk so trampoline can manage the iteration safely. return () => // Call f in CPS form: // f decides how to compute/update the accumulator, and then calls k2(newAcc). f(x, accNow, (newAcc) => { // After f computes newAcc, continue folding from i+1. // step(i + 1, newAcc) returns a thunk, which trampoline will eventually run. return step(i + 1, newAcc); }); }; // Start the fold at index 0 with the initial accumulator acc. // step(0, acc) returns the initial thunk; trampoline runs it to completion. return trampoline(() => step(0, acc)); };