Created
July 16, 2011 11:42
-
-
Save armstrjare/1086285 to your computer and use it in GitHub Desktop.
A proof of concept that Ruby's native catch/throw can be [almost*] re-implemented using Ruby continuations.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# A proof of concept that Ruby's native catch/throw can be [almost*] re-implemented in Ruby. | |
# | |
# * begin/ensure semantics don't work when calling a Continuation | |
# -- so, unfortunately this implementation is not semantically equivalent to Ruby's native version :( But still cool nonetheless | |
module ContinuationCatchThrow | |
require 'continuation' unless defined?(Continuation) | |
# Emulate ruby Kernel#catch | |
def my_catch(tag = Object.new, &blk) | |
# This is a global stack that keeps track of the current "catch" blocks we're in. | |
# It's an array of arrays, where the first value is the tag we're catching (or nil if 'any tag'). | |
# The second value is a continuation to bring the program state back to the point it was at when the catch block was entered. | |
Thread.current[:__catch_tags] ||= [] | |
# Count the number of catches on the stack. | |
# We use this to reset the state after leaving a catch block | |
catches = Thread.current[:__catch_tags].length | |
# Return caught thrown value (unless returned from catch block) | |
return callcc { |cc| | |
Thread.current[:__catch_tags].push([tag, cc]) | |
# Yield the catch block and return it (i.e. no throw) | |
return yield(tag) | |
} | |
ensure | |
# Reset the stack to what it was when we entered this catch call. | |
Thread.current[:__catch_tags].slice!(catches, Thread.current[:__catch_tags].length) | |
end | |
# Emulate Kernel#throw | |
def my_throw(tag, value = nil) | |
Thread.current[:__catch_tags] ||= [] | |
# Detect the catcher | |
catcher_index = Thread.current[:__catch_tags].rindex { |item| item[0].nil? || item[0] == tag } | |
# We have to find a catcher | |
raise ArgumentError, "uncaught throw #{tag.inspect}" unless catcher_index | |
# Call the continuation set for this catcher | |
catcher = Thread.current[:__catch_tags][catcher_index] | |
catcher[1].call(value) | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment