Continuation example

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
 
E

ES

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!

Hehe, strange, I was lamenting the state of exception handling on the bus just
yesterday. I was thinking along the same lines, some sort of detached exception
handling mechanism. The handler would be defined elsewhere (so as to not clutter
the method) but would probably have to have access to the method's context.

Ruby's positioned well as far as 'normal' exception handling with having allowed
def ... rescue along with the explicit mode, and particularly with incorporating
'retry' (if you don't try to handle the exception, you could just fail with an
ERRNO anyway:). Now if only the default handling were detached from the method..

It'd be great fun if exception handling could *literally* be described
parallel to the method :)

<pre>

def foo() handler
# Do something
do_something() fail? do fix_do_something(); retry :eek:nce; end

# Other ops
if something_else
do_something_else() fail? do fix_do_something_else(); retry 2; end
end
end end

</pre>

Good work.
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

Jos Backus

E
 
R

Robert Klemme

Jos Backus said:
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!

I don't really see the need for continuations here. Note that the handler
can do anything - even raise exceptions.

11:19:18 [robert.klemme]: /c/temp/ruby/nocont.rb
1
2
Error for element 3
3
4
5
Error for element 6
6
7
8
Error for element 9

Kind regards

robert



#!/usr/bin/env ruby

MAX_ERRORS = 3

class Reader
HANDLER = proc {|e| $stderr.puts "Error for element #{e.inspect}"}

def initialize(arr)
@arr = arr
end
def each(check, handler = HANDLER)
errors = 0
cc = nil
@arr.each do |elem|
if check.call(elem)
handler.call(elem)
errors += 1
break if errors >= MAX_ERRORS
end
yield elem
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}
rdr.each(check) do |elem|
puts elem
end
 
M

Mathieu Bouchard

It'd be great fun if exception handling could *literally* be described
parallel to the method :)

You need "around-methods", an AOP concept taken from CommonLISP. You
define the error handler as an around-method called foo, put a
begin/rescue/ensure block that normally just runs "super", which calls the
supermethod, which is not necessarily in a superclass. In this case, what
gets run is the regular-method foo of the same class.

You can achieve a similar effect without AOP by putting error handling in
foo() and the code in foo_real().

An alternative is to have two classes instead, and put the error-handling
in the subclass. This is what MetaRuby (matju's) did to achieve
contract-checking. However it doesn't lend itself to subclassing, as the
first foo is executed inside the error handling (the 2nd foo) but the 3rd
foo is executed outside of it.

_____________________________________________________________________
Mathieu Bouchard -=- Montréal QC Canada -=- http://artengine.ca/matju
 
J

Jos Backus

On Wed, March 2, 2005 12:04 am, Jos Backus said: [snip]
It'd be great fun if exception handling could *literally* be described
parallel to the method :)

I'm still trying to wrap my head around this. It sounds like you want a
special kind of method attribute that is itself a method that is invoked under
certain circumstances?
<pre>

def foo() handler
# Do something
do_something() fail? do fix_do_something(); retry :eek:nce; end

# Other ops
if something_else
do_something_else() fail? do fix_do_something_else(); retry 2; end
end
end end

</pre>

Good work.

Thanks.

Two nits I found after sending:
 
E

ES

On Wed, March 2, 2005 12:04 am, Jos Backus said: [snip]
It'd be great fun if exception handling could *literally* be described
parallel to the method :)

I'm still trying to wrap my head around this. It sounds like you want a
special kind of method attribute that is itself a method that is invoked under
certain circumstances?

Oh, no, more of a concrete separation of exception handling from the method
body: conceptually think IDE-level[1]. You specify the correct code path in
your method (i.e. write it like nothing could ever go wrong), then double-click
on the method name and it brings up a little window where you can enter all
of the exception handling code[2]. Obviously this in particular is more a
convenience/clutterlessness enhancement, but it might help in redirecting
exception processing back to 'handling' instead of 'reporting'.

[1] IDE used just for visualization of example, no reason this couldn't
be used at any level from language specification up.
[2] Exception handling, says I, should exist as a meta-level in a given
program without the access restraints placed on normal code.

So here,

This is the method This is the error handling, tab-separated :)
| |
V V
Thanks.

Two nits I found after sending:


Jos Backus

E
 
J

Jos Backus

On Wed, March 2, 2005 12:04 am, Jos Backus said: [snip]
It'd be great fun if exception handling could *literally* be described
parallel to the method :)

I'm still trying to wrap my head around this. It sounds like you want a
special kind of method attribute that is itself a method that is invoked under
certain circumstances?

Oh, no, more of a concrete separation of exception handling from the method
body: conceptually think IDE-level[1]. You specify the correct code path in
your method (i.e. write it like nothing could ever go wrong), then double-click
on the method name and it brings up a little window where you can enter all
of the exception handling code[2]. Obviously this in particular is more a
convenience/clutterlessness enhancement, but it might help in redirecting
exception processing back to 'handling' instead of 'reporting'.

Okay, I think I understand the IDE analogy. So these around-methods are a way
of implementing this at the language level (I know nothing about AOP btw)?
Sounds interesting...

[example snipped]
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,967
Messages
2,570,148
Members
46,694
Latest member
LetaCadwal

Latest Threads

Top