Stack safe recursion sometimes produces deferred nested function call trees, which, once evaluated may exhaust the stack:
const compn = fs => // non-recursive to keep it simple
fs.reduce((f, g) => comp(f) (g), x => x);
const compn_ = fs => x => // non-recursive to keep it simple
fs.reduce((x, f) => f(x), x);
const comp = f => g => x => f(g(x));
const inc = x => x + 1;
const fs = Array(1e5).fill(inc);
try {compn(fs) (0)}
catch (e) {
console.log(e.message);
}
console.log(compn_(fs) (0));
We can avoid building such structures altogether (compn_
), yet I wonder if there is a less limited way to deal with them:
const rec = f => (...args) => {
let step = f(...args);
const stack = [];
while (step.tag !== Base) {
stack.push(step.f);
step = f(...step.step.args);
}
return x => {
for (let i = stack.length - 1; i >= 0; i--) {
x = stack[i] (x);
if (x && x.tag === Base) {
x = x.x;
break;
}
}
return x;
}
};
const Base = x =>
({tag: Base, x});
const Call = (f, step) =>
({tag: Call, f, step});
const Step = (...args) =>
({tag: Step, args});
const id = x => x;
const comp = f => g => x => f(g(x));
const inc = x => x + 1;
const fold = f => acc => xs =>
rec(i =>
i === xs.length
? Base(acc)
: Call(f(xs[i]) (id), Step(i + 1))) (0);
// ^^ works but is nonsensical
const fs = Array(1e5).fill(inc);
console.log(
fold(f => g => comp(f) (g))
(id)
(fs)
(0)); // 100000
This works but applying id
to each recursive step defeats the purpose of using comp
in the first place. Is there a way to work with such deferred tree structures in Javascript or do we have to fall back to linear, stack-like structures?