Kevin Smith <sent / qualitycode.com> writes: >Correct me if I'm wrong, but it seems like Ruby's >catch/throw takes care of this "abuse" of >exceptions. If you want a clean way to exit deep >nesting, use catch/throw. If you have a true >error condition, use an exception. Right? You are right. But I wouldn't term it an "abuse". >Before reading this piece, I hadn't put together >why it would make sense to support both >mechanisms. If there are other angles of this >topic, I'd be interested in hearing about them. As with many things, the reason is historic. Common Lisp absorbed catch/throw from MacLisp (to do with MIT's project MAC of the 60s and not the friendly little desktop :-)). It provides a convenient -dynamic- non-local exit mechanism. But the problem was that you could leave your computation in an unstable state: 1. open a file 2. start processing it 3. close it A throw from #2 would skip #3. Hence the 'unwind-protect' form was introduced: (IIRC Richard Stallman is credited with this) (unwind-protect (prog2 (open file) (process file) ; prog2 was also introduced for this reason (close file))) ; it returns not the first but the value of the 2nd form But, hey, thats what Java/Python's 'finally' and Ruby's 'ensure' is supposed to do, right? Exactly! Remember, the reason is historic. Exceptions (or in Lisp lingo 'conditions') were introduced later. Kent Pitman's conditions proposal was introduced into Common Lisp only in Guy Steele's 2nd book circa 1990. With the equivalent of ensure/finally the need for 'unwind-protect' is -almost- moot. (Some prefer it's syntactic convenience to exception handling.) Bottom line: if you are going to use catch/throw be sure you don't need an clean-up action the non-performance of which might leave your system in an unstable state. Here is some pedagogical code the illustrates the difference: def tst1 puts "Line 1" x = catch (:foo) { begin puts "Line 2" throw :foo, "ball" puts "Should not see this" rescue puts "Unfortunately neither will this be seen" end } puts "x = #{x}" end class Foo < RuntimeError end def tst2 puts "Line 1" begin puts "Line 2" raise Foo, "ball" puts "Should not see this" rescue => x "no op" # just to mirror the above code as close as possible ensure puts "A non-local exit has occurred!" end puts "x = #{x}" end ruby> tst1 Line 1 Line 2 x = ball ruby> tst2 Line 1 Line 2 A non-local exit has occurred! x = ball Hence 'exceptions' are more general/powerful than catch/throw --- Python's choice can be justified. Ruby is an interesting amalgamation of concepts from various languages (i've started viewing it as having the evaluation model of Lisp/Scheme + the object model of Smalltalk + conventional syntax). But continuations are even more powerful (too powerful in some folks opinion). All of the functionality of catch/throw and exceptions can be emulated with continuations + macros (as folks in the Scheme world do). This raised another interesting issue: OK, finally/ensure cleans thing up on the way -out- ... but what do you do if you grab a continuation, perform a non-local exit with clean up (e.g. raising an exception) and then later invoke the continuation. How do you conveniently "un-do" the cleanup actions when you re-enter a captured state? Enter Friedman/Haynes/Dybvig's concept of 'dynamic-wind' a generalization of Stallman's 'unwind-protect' in the presence of full continuations ... but thats another story ;-) Raja