From: Christopher Dicely Date: 2009-02-27T06:03:33+09:00 Subject: Re: Common Lisp conditions On 2/23/09, Gavin Sinclair wrote: > Hi, > > So far as the search engine on this newsgroup is concerned, there's > been no discussion about including a Common Lisp-style condition > system in a future version of Ruby. For information about these, [1] > is a good readable resource. > > For those who don't know what they offer above normal exception- > handling (as we know it in Ruby, Java, C++, ...), conditions need not > be errors and they need not unwind the stack. A low-level function > can signal that something is wrong, and provide multiple ways of > handling it (which is appropriate, because the handling code is low- > level). A high-level function can receive the signal and choose which > low-level handler to run, or can "handle" it itself or ignore it (the > latter two options being what's available in Ruby etc.). Another good > example is low-level code issuing a warning and higher-level code > deciding whether to print it. It's _very_ elegant. > > For those who _do_ know about Common Lisp conditions, is there any > appetite for them in Ruby? I see great positives and no negatives. > They don't seem at all un-Ruby-like; in fact the relationship between > high- and low-level code reminds me of the elegance of blocks and > yield. > > After a small amount of thought, I haven't come up with a suitable > syntax. That's probably because I haven't used the things, only read > about them. > > Just curious about people's thoughts, and quietly hopeful for Ruby > 2.0 :) > > Gavin > > [1] > http://gigamonkeys.com/book/beyond-exception-handling-conditions-and-restarts.html > > Exceptions don't need to be errors in Ruby, though conventionally they are used for that and throw/catch for similar non-errors. As far as the additional functionality of conditions, its not that hard to implement at least what I see as the most important one (restarts) on top of Ruby's existing exception-handling using continuations, e.g., in Ruby 1.8 (and, I would presume, Ruby 1.9 with the addition of "require 'continuation'" at the top: # This code is untested on any but the demo case at the end. class Condition < Exception FAIL = Object.new def self.alert(*args) block = (block_given? ? lambda { |c| yield c } : nil) callcc { |cont| raise self.new(cont, block, *args) } end def initialize(c, block, *args) @continuation = c @restarts = {} block.call(self) if block end def add_restart(restart_name, &block) @restarts[restart_name] = lambda do |*args| result=block.call(self, *args) @continuation.call(*result) unless result==FAIL nil end self end def restarts @restarts.dup end def restart(restart_name, *args) @restarts[restart_name].call(*args) if @restarts.include? restart_name end end class BadValueCondition < Condition attr_reader :value def initialize(c, block, val, *args) @value = val super end end def process_value(val) if (val.div 2) == (val.quo 2) BadValueCondition.alert(val) else val.to_s end end def process(range) range.map { |val| process_value(val) }.compact rescue BadValueCondition => c c.add_restart(:ignore) {} c.add_restart(:substitute_warning) { |c, *args| "Bad: #{c.value}" } raise end [:ignore, :substitute_warning].each do |strategy| begin puts "\n---\nDemonstrating restart #{strategy.inspect}" arr = process(1..10) puts arr.join("\n") rescue BadValueCondition => c c.restart(strategy) raise end end