The code doesn't work because the loop counter in k.times
is stuck. Each call to entry.call(entry)
rewinds the program to when callcc
returns. So callcc
returns again, the later work happens again, work
returns again, and k.times
starts again. When k.times
starts, it resets its loop counter to zero. The infinite loop is because the loop counter is always zero.
To fix the program, we must continue the loop, not restart it. The best fix is to use a fiber, but first, I try to use a continuation. Here's the version that works on my machine:
require 'continuation'
def work
p "early work"
here = callcc {|here| here}
p "later work"
return here
end
class Integer
def my_times
i = 0
while i < self
yield i
i += 1
end
end
end
def rework(k)
entry = nil
k.my_times do |i|
if i == 0
entry = work
else
entry.call(entry)
end
end
end
rework(2)
I fix the control flow by calling work
inside the loop. When work
returns again, I don't reset the loop counter.
I also define my own Integer#my_times
and don't use Ruby's Integer#times
. If I change the code from k.my_times
back to k.times
, the loop counter gets stuck again. This exposes a problem with continuation objects in Ruby.
When a continuation rewinds a program, it might rewind or preserve the values of local variables. My program assumes that entry.call
preserves the loop counter. Matz's Ruby Implementation preserves the loop counter in Integer#my_times
, but rewinds the loop counter in Integer#times
. This is the only reason why my program can't use Integer#times
.
MRI seems to rewind locals in C code (like Integer#times
) but preserve locals in Ruby code (like Integer#my_times
). This makes a mess of loop counters and other locals. Ruby does not fix this mess, but warns against callcc
. Ruby says, warning: callcc is obsolete; use Fiber instead
.
Here's the program using a fiber:
def work
p "early work"
here = Fiber.new do
while true
p "later work"
Fiber.yield
end
end
here.resume
return here
end
def rework(k)
entry = nil
k.times do |i|
if i == 0
entry = work
else
entry.resume
end
end
end
rework(2)