public
Created

  • Download Gist
delimcont.rb
Ruby
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
RESET_STACK = []
SHIFT_MAP = {}
 
def reset(&block)
RESET_STACK.push block
block.call()
ensure
RESET_STACK.pop
end
 
def shift(&block)
if SHIFT_MAP.key? block.source_location
ary = SHIFT_MAP[block.source_location]
value = ary.pop
SHIFT_MAP.delete(block.source_location) if ary.empty?
value
else
shift_proc = proc do |arg|
SHIFT_MAP[block.source_location] ||= []
SHIFT_MAP[block.source_location].push arg
RESET_STACK[-1].call
end
block.call(shift_proc)
end
end
 
p reset { 2 * 2 } # 4
p reset { 2 * shift {|k| 2 } } # 4
p reset { 2 * shift {|k| k[2] } } # 8
p reset { 2 * shift {|k| k[k[2]] } } # 16
p reset { 2 * shift {|k| k[shift {|j| j[3]}] } } # 24
p reset { 2 * shift {|k| k[3] + k[4]} } # 28

@stephenb: Duh, thanks for the correction.

Yeah, of course this impl only works downstream from reset; you can't actually save off the continuation and call it from outside reset. That would only be possible with a library or JVM support for real continuations/coroutines.

My version obviously also deepens the stack for the nested continuation invokes, where a Lisp or Scheme VM would probably TCO some of those away.

I'm interested in understanding more about shift-reset and was looking for more interesting examples where it would be useful and came across Christian's article.

Then I was trying to work out how the two implementations differed and why his example didn't work with your implementation. Stepping through the code in the different implementations was a bit of a mind-bender. Haven't used continuations in Ruby before.

Does the way you've implemented shift-reset prevent creating some kind of lazy forkable iterator similar to what Christian made with each_to_stream(collection)?

Please sign in to comment on this gist.

Something went wrong with that request. Please try again.