This post made me revisit something I’d shelved. Last summer I built an algebraic effects system in Nix (free monad encoding, handler composition, dependent contracts, the works) but the handler loop was direct recursion. Anything beyond a few modules just blew the C++ stack. I figured stack depth was a death sentence for the whole approach and gave up on it.
Looking at it fresh, turns out builtins.genericClosure works as a trampoline. It’s the only iterative primitive Nix has, and if you force handler state through deepSeq in the key field it breaks thunk accumulation. O(1) stack depth at 1M+ operations. Wrote it up here: Trampolining Nix with genericClosure
I come from Common Lisp, so the conditions/restarts module stood out. Storing restart closures in the context and invoking by name is basically restart-case. I did something similar in nix-effects, different encoding but same idea. You can’t get the interactive restart menu without something like IFD, but for configuration where restarts are known ahead of time, that’s fine.
Anyway, have you run into stack depth on larger computations, or does the lazy context-passing just sidestep it?