J
Jos Backus
Here's an example I came up with today while investigating continuations. Hope
this is useful to somebody trying to understand and use them. Feedback
welcome!
Output:
lizzy:~% ./continuation-example.rb
1
2
exception seen, performing error handling (elem=3)
continuing
4
5
exception seen, performing error handling (elem=6)
continuing
7
8
exception seen, performing error handling (elem=9)
continuing
lizzy:~%
Code:
#!/usr/bin/env ruby
# Have you ever had the problem of wanting to report some error condition from
# deep down within a set of nested method calls, but also wanting processing
# to continue right after the spot where the error occurred, after the error
# is reported and handled in the caller?
#
# While there are other ways to do this (e.g. using callbacks), this example
# shows how to do this using exceptions and continuations. The trick is to
# have the exception propagate the continuation to the appropriate caller
# which can then handle the error (print an error message, say) and cause
# processing to continue inside the method that saw the error by calling the
# continuation passed up.
#
# This code uses a Reader class instance to process a list of elements
# (numbers) using a screening function. The processing is facilitated using
# the Reader.each method. Inside this method, if the element passes screening
# it is yielded to the caller, else an exception is raised. The caller
# processes the exception, then causes processing to continue inside the
# Reader.each method, while passing a hint into it telling it how to continue.
# In this particular example, the screening test consists of checking whether
# a list element is divisible by 3, if which case it is considered "bad".
# Also, when a maximum of 3 errors is seen (as indicated by the caller)
# processing of list elements stops.
MAX_ERRORS = 3
class CCException < Exception
attr_reader :cc, :elem
def initialize(cc, elem)
@cc, @elem = cc, elem
end
end
class Reader
def initialize(arr)
@arr = arr
end
def each(check)
cc = nil
@arr.each do |elem|
if check.call(elem)
ok_to_continue = Kernel.callcc do |cc|
raise CCException.new(cc, elem)
end
break unless ok_to_continue
else
yield elem
end
end
end
end
list = [1,2,3,4,5,6,7,8,9,10,11,12]
rdr = Reader.new(list)
errors = 0
# Returns whether we consider an element 'bad'
check = proc {|elem| elem % 3 == 0}
begin
rdr.each(check) do |elem|
puts elem
end
rescue CCException => exc
errors += 1
puts "exception seen, performing error handling (elem=#{exc.elem})"
# ...error handling...
puts "continuing"
exc.cc.call(errors < MAX_ERRORS)
end
exit
this is useful to somebody trying to understand and use them. Feedback
welcome!
Output:
lizzy:~% ./continuation-example.rb
1
2
exception seen, performing error handling (elem=3)
continuing
4
5
exception seen, performing error handling (elem=6)
continuing
7
8
exception seen, performing error handling (elem=9)
continuing
lizzy:~%
Code:
#!/usr/bin/env ruby
# Have you ever had the problem of wanting to report some error condition from
# deep down within a set of nested method calls, but also wanting processing
# to continue right after the spot where the error occurred, after the error
# is reported and handled in the caller?
#
# While there are other ways to do this (e.g. using callbacks), this example
# shows how to do this using exceptions and continuations. The trick is to
# have the exception propagate the continuation to the appropriate caller
# which can then handle the error (print an error message, say) and cause
# processing to continue inside the method that saw the error by calling the
# continuation passed up.
#
# This code uses a Reader class instance to process a list of elements
# (numbers) using a screening function. The processing is facilitated using
# the Reader.each method. Inside this method, if the element passes screening
# it is yielded to the caller, else an exception is raised. The caller
# processes the exception, then causes processing to continue inside the
# Reader.each method, while passing a hint into it telling it how to continue.
# In this particular example, the screening test consists of checking whether
# a list element is divisible by 3, if which case it is considered "bad".
# Also, when a maximum of 3 errors is seen (as indicated by the caller)
# processing of list elements stops.
MAX_ERRORS = 3
class CCException < Exception
attr_reader :cc, :elem
def initialize(cc, elem)
@cc, @elem = cc, elem
end
end
class Reader
def initialize(arr)
@arr = arr
end
def each(check)
cc = nil
@arr.each do |elem|
if check.call(elem)
ok_to_continue = Kernel.callcc do |cc|
raise CCException.new(cc, elem)
end
break unless ok_to_continue
else
yield elem
end
end
end
end
list = [1,2,3,4,5,6,7,8,9,10,11,12]
rdr = Reader.new(list)
errors = 0
# Returns whether we consider an element 'bad'
check = proc {|elem| elem % 3 == 0}
begin
rdr.each(check) do |elem|
puts elem
end
rescue CCException => exc
errors += 1
puts "exception seen, performing error handling (elem=#{exc.elem})"
# ...error handling...
puts "continuing"
exc.cc.call(errors < MAX_ERRORS)
end
exit