Split off from #46.
#46 fixes the Lua parser nesting limit (chunk has too many syntax levels, LUAI_MAXCCALLS ≈ 200) for the common case by adding magic-do for Effect and ST: their bind/discard/pure chains compile to a flat statement sequence instead of nested continuations.
That leaves the general case: a long straight-line do/>>= chain in any other monad (Maybe, Either, Aff, State, Writer, a custom parser/decoder, or a large applicative ado) still emits ~200+ lexically nested closures and hits the same parser limit.
Why this matters specifically for the Lua backend
On the JS/ES backends deep nesting is only a performance cost — their parsers have no comparable cap. On Lua it is a correctness failure: the file will not even load. So, unlike upstream, we eventually need a backend-agnostic nesting breaker, not just Effect/ST magic-do.
Why it is harder than magic-do
magic-do works because Effect is \() -> ... (a thunk we can inline as local x = m()). For a general monad, bind m k is a real call with monad-specific semantics — it cannot be turned into a sequential local. The naive "hoist continuations into named locals" does not work either: a x <- m continuation captures x, and a later action may reference earlier xs, so the continuation cannot leave the enclosing scope.
A real fix needs one of:
- a CPS / trampoline-style transform, or
- chunking the chain into helper functions that thread the live environment as explicit parameters, breaking the chain into segments each below the nesting cap.
Realistic triggers
Uncommon in hand-written code; realistic in machine-generated code, decoders/validators for very wide records, and large ado constructors (and Aff, if/when it is ported to Lua). Lower priority than #46 accordingly.
Acceptance
A do block of N ≫ 200 statements in a non-Effect monad (e.g. a long Maybe/State chain) compiles to Lua that loads and runs correctly under stock Lua 5.1.
Split off from #46.
#46 fixes the Lua parser nesting limit (
chunk has too many syntax levels,LUAI_MAXCCALLS≈ 200) for the common case by adding magic-do for Effect and ST: theirbind/discard/purechains compile to a flat statement sequence instead of nested continuations.That leaves the general case: a long straight-line
do/>>=chain in any other monad (Maybe,Either,Aff,State,Writer, a custom parser/decoder, or a large applicativeado) still emits ~200+ lexically nested closures and hits the same parser limit.Why this matters specifically for the Lua backend
On the JS/ES backends deep nesting is only a performance cost — their parsers have no comparable cap. On Lua it is a correctness failure: the file will not even load. So, unlike upstream, we eventually need a backend-agnostic nesting breaker, not just Effect/ST magic-do.
Why it is harder than magic-do
magic-do works because Effect is
\() -> ...(a thunk we can inline aslocal x = m()). For a general monad,bind m kis a real call with monad-specific semantics — it cannot be turned into a sequentiallocal. The naive "hoist continuations into named locals" does not work either: ax <- mcontinuation capturesx, and a later action may reference earlierxs, so the continuation cannot leave the enclosing scope.A real fix needs one of:
Realistic triggers
Uncommon in hand-written code; realistic in machine-generated code, decoders/validators for very wide records, and large
adoconstructors (andAff, if/when it is ported to Lua). Lower priority than #46 accordingly.Acceptance
A
doblock of N ≫ 200 statements in a non-Effect monad (e.g. a longMaybe/Statechain) compiles to Lua that loads and runs correctly under stock Lua 5.1.