Ruby has continuations... does it have a dynamic-wind
construct like Scheme?
views:
95answers:
1[This answer is written with Scheme programmers in mind (the OP has asked other Scheme questions here before, so that's a safe bet). If you're here because you're a Ruby programmer with no Scheme background, read the footnote for some context. :-)]
MRI doesn't (see below); and if MRI doesn't, that means there is no portable way to use any such functionality even if another implementation provides it.
I actually inspected the MRI 1.9.1 source code, just to be sure. In any case, here is some code to demonstrate that even the normal unwind protection (ensure
) doesn't work correctly with continuations on MRI (tested with both 1.8.7 and 1.9.1). (It does work correctly with JRuby (I tested with 1.5), so it goes to show it's an implementation-specific thing. But note that JRuby only provides escape continuations, not general-purpose ones.)
callcc do |cc|
begin
puts 'Body'
cc.call
ensure
puts 'Ensure'
end
end
(To test with MRI 1.9+, you need to either run with the -rcontinuation
option, or put require 'continuation'
at the top of the file.)
For readers who don't know what a dynamic-wind
is, it's a way to specify code to be run when the code being covered is exited (much like ensure
), as well as code to to be run when the covered code is re-entered. (This can happen when you use call/cc
inside the covered code, and invoke the continuation object after the covered code has been exited.)
Totally contrived example:
def dynamic_wind pre, post, &block
raise 'Replace this with a real implementation, kthx'
end
def redirect_stdout port, &block
saved = $stdout
set_port = lambda {$stdout = port}
reset_port = lambda {$stdout = saved}
dynamic_wind set_port, reset_port, &block
end
cc = nil
# cheap way to nuke all the output ;-)
File.open '/dev/null' do |null|
redirect_stdout null do
callcc {|cc|}
puts 'This should not be shown'
end
puts 'This should be shown'
cc.call
end
So, a properly-functioning dynamic_wind
implementation would ensure that $stdout
would be set back to the /dev/null
stream when the continuation is invoked, so that at all instances where puts 'This should not be shown'
is run, that text is indeed not shown.