The reason that you don't get a stack overflow here because the way you're using shift
and callback()
is acting like a trampoline.
Each time the execution thread reaches the shift
construct, it sets callback
equal to the current continuation (a closure), and then immediately returns Unit
to the calling context. When you call next()
and invoke callback()
, you execute the continuation closure, which just executes count += 1
, then jumps back to the beginning of the loop and executes the shift
again.
One of the key benefits of the CPS transformation is that it capture the flow of control in the continuation rather than using the stack. When you set callback = f
on each "iteration" you're overwriting your only reference to the previous continuation/state of the function, and that allows it to be garbage collected.
The stack here only ever reaches a depth of a few frames (it's probably around 10 because of all the nested closures). Each time you execute the shift
it captures the current state in a closure (in the heap), and then the stack unrolls back to your for
expression.
I feel like a diagram would make this clearer—but stepping through the code with your debugger would probably be just as useful. I think the key point here is, since you've essentially built a trampoline, you'll never blow the stack.