Problem
magicDo wraps a do-block's final action in a run application: App finalAction EffectRunArg. When the final action is a conditional, which is the standard shape of a tail-recursive effect loop (do logShow n; if n <= 1 then log "done" else loop (n - 1)), the marker applies to the whole IfThenElse. Codegen then emits an IIFE that returns the selected action plus a second call that forces it: (function() if ... then return A else return B end end)()(). Two costs follow. Each executed conditional tail pays an extra closure and two extra calls, and the branch call loop (n - 1) stays partial because the run sits outside the if, so the self-call never becomes a saturated worker call: loopification (#181) sees no tail self-call and cannot rewrite the recursion into while true.
Approach
Rewrite App (IfThenElse c t e) EffectRunArg to IfThenElse c (App t EffectRunArg) (App e EffectRunArg). Selecting the action first and running it second is the same evaluation order, so the rewrite preserves semantics; it consumes the outer application while adding one marker node per branch, so it terminates and cannot re-fire on its own output. Placed before the late uncurry run (#200), the pushed-in runs saturate the branch spines: loop(n-1)(run) becomes loop$w(n-1, run), which the Lua backend emits as the tail call return M.loop_S_w(n - 1), and loopification turns the recursion into a loop.
Prerequisites / Relations
Unlocks the #181 synergy sketched in #200 for if-terminated effect loops; the Golden/UncurryEffect countdown documents today's wrapper round-trip. Nested conditionals distribute recursively.
Open questions
- Where the rewrite lives: magicDo's
buildThunk could emit per-branch runs directly, while an optimizer rule would also catch run-of-if shapes from other producers such as ForeignLift.
- Interaction with the if-folding rules (
removeIfWithEqualBranches, the boolean folds): distribution strictly consumes the outer run, so fixpoint convergence should hold, but the rule ordering deserves a test.
Problem
magicDo wraps a do-block's final action in a run application:
App finalAction EffectRunArg. When the final action is a conditional, which is the standard shape of a tail-recursive effect loop (do logShow n; if n <= 1 then log "done" else loop (n - 1)), the marker applies to the wholeIfThenElse. Codegen then emits an IIFE that returns the selected action plus a second call that forces it:(function() if ... then return A else return B end end)()(). Two costs follow. Each executed conditional tail pays an extra closure and two extra calls, and the branch callloop (n - 1)stays partial because the run sits outside the if, so the self-call never becomes a saturated worker call: loopification (#181) sees no tail self-call and cannot rewrite the recursion intowhile true.Approach
Rewrite
App (IfThenElse c t e) EffectRunArgtoIfThenElse c (App t EffectRunArg) (App e EffectRunArg). Selecting the action first and running it second is the same evaluation order, so the rewrite preserves semantics; it consumes the outer application while adding one marker node per branch, so it terminates and cannot re-fire on its own output. Placed before the late uncurry run (#200), the pushed-in runs saturate the branch spines:loop(n-1)(run)becomesloop$w(n-1, run), which the Lua backend emits as the tail callreturn M.loop_S_w(n - 1), and loopification turns the recursion into a loop.Prerequisites / Relations
Unlocks the #181 synergy sketched in #200 for if-terminated effect loops; the
Golden/UncurryEffectcountdown documents today's wrapper round-trip. Nested conditionals distribute recursively.Open questions
buildThunkcould emit per-branch runs directly, while an optimizer rule would also catch run-of-if shapes from other producers such as ForeignLift.removeIfWithEqualBranches, the boolean folds): distribution strictly consumes the outer run, so fixpoint convergence should hold, but the rule ordering deserves a test.