=> (try (doall (map (partial / 36) [4 9 3 2 0])) (catch
ArithmeticException _ "boo!"))
#<CompilerException java.lang.RuntimeException:
java.lang.ArithmeticException: Divide by zero (NO_SOURCE_FILE:0)>

The problem with trying to catch exceptions thrown from inside lazy
sequence generators is that they all get wrapped in RuntimeException.

Catching that works:

=> (try (doall (map (partial / 36) [4 9 3 2 0])) (catch
RuntimeException _ "boo!"))
"boo!"

but doesn't allow catch-dispatch on exception type.

You can get the final cause of an exception with this:

(defn get-cause [t]
  (loop [t t]
    (if-let [c (.getCause t)]
      (recur c)
      t)))

 You could make a catch-last macro.

(defmacro catch-last [& catches]
  `(catch Throwable t#
     (let [cause# (get-cause t#)]
       (try (throw cause#)
         ~@catches))))

seems like it should work, and macroexpands properly, but fails oddly:

=> (try (doall (map (partial / 36) [4 9 3 2 0])) (catch-last
ArithmeticException _ "boo!"))
#<CompilerException java.lang.Exception: Unable to resolve symbol:
catch in this context (NO_SOURCE_FILE:1)>

If you ask me, that's a compiler bug right there; it should expand to
(try (doall ...) (catch (let ...))) which is legal Clojure and a legal
try-catch form in particular.

This, instead, works:

(defmacro try-2 [& body]
  (let [[try-part catches] (split-with #(not= (first %) 'catch) body)
        [catches finally] (split-with #(not= (first %) 'finally) catches)]
    `(try
       ~@try-part
       (catch Throwable t#
         (let [cause# (get-cause t#)]
           (try (throw cause#)
             ~@catches)))
       ~@finally)))

=> (try-2 (doall (map (partial / 36) [4 9 3 2 0])) (catch
ArithmeticException _ "boo!"))
"boo!"

However, if you call a Java API that wraps exceptions, you might want
to catch the Java layer exception and not its own cause. That is, you
could have

Exception in thread "Thread-1989" java.lang.RuntimeException: blah blah
        at <stack trace here>
Caused by com.foo.FooFrameworkLoadResourceException: blah blah
        at <stack trace here>
Caused by org.xml.sax.SAXException: blah blah
        at <stack trace here>

and you probably don't care that the framework encountered some
malformed XML on the web, but that it failed to load a resource.
Ordinary try will catch the RuntimeExcepion and try-2 will catch the
SAXException; neither will catch the
FooFrameworkLoadResourceException.

The simplest thing Clojure could do to alleviate this type of
difficulty is to not wrap in generic RuntimeExceptions. Instead,
derive ClojureRuntimeException from it and use that, and we can
redefine get-cause as follows:

(defn get-cause [t]
  (loop [t t]
    (if (instance? ClojureRuntimeException t)
      (recur (.getCause t))
      t)))

and then try-2 will behave as desired for cases like this.

But then there's so much more that could be done, such as allowing
error handlers to be called as soon as various conditions arise that
can perform restarts. The options would generally be to a) propagate
the exception, b) retry, c) throw an Error, and d) return some other
value as the value of the expression whose evaluation aborted. So if
we set up to propagate, the behavior would be the same as now. On the
other hand if we set up to restart, we might use a handler like:
(syntax speculative)

(handler ArithmeticException _ Double/NaN)

in case we'd rather have NaNs propagate through failed math, for example, or

(handler IOException _ nil)

if we want some I/O read operation to return nil on failure. How about

(handler OutOfMemoryError _ (reset! cache {}) (retry))

with the proviso that retry reruns the failed expression as if retry
were a zero-argument function containing it as its body and any
unhandled exceptions thrown out of a handler are propagated. This
allows propagation either via (handler FooException e (throw e)) or by
not having a handler registered for FooException. Multiple retries
would require try...catch inside the handler.

The default in REPL sessions could even be to pop up a message box via
Swing and prompt the user what to do, not unlike the errors one gets
developing in typical Common Lisp environments:



! Error                                   x _ #

         FooException in foo-function

 (x) Propagate the exception
 ( ) Retry
 ( ) Throw Error
 ( ) Evaluate to this value: [nil            ]

 (Copy stack trace to clipboard)          (OK)



where the field for the third option is of course passed through
read-string or maybe even eval and "throw Error" would be omitted if
the exception was already an Error.

The proposed handler syntax deliberately looks like existing catch clauses.

The hairy details include how one would set handlers -- updating a
thread-local map of exception types to handler closures? The default
for any given exception class would be propagate, of course. Another
hairy detail would be how to resolve conflicts -- also, when to invoke
handlers. Given the need for a handler to capture the failed
evaluation, it might require something like wrapping function bodies
in a try ... catch that punts to the handlers or rethrows as needed.
Lazy sequence arguments' heads will get held until the function
returns, which may be very bad. It might be hard to make this work and
have acceptable performance if it's dynamic.

For now, though, at the very least try-2 and the notion of a
ClojureRuntimeException wrapper class in future versions of Clojure
are viable improvements.

(Interestingly, the try-2 macro would cause the catch-last behavior
observed if it weren't already happening and you used catch-last
inside try-2. This indicates that try is being expanded like a macro
instead of processed after macroexpansion. Other special forms exhibit
similar behavior, e.g. you can't have def's first argument come out of
a macro expansion unless the whole def form does, but it's unexpected
in the case of try because (catch ...) and (finally ...) "feel like"
special forms in their own right, which certainly should not work
outside of a try but seem like they should be separately generatable
by macros the way other forms in the try body can be.)

-- 
Protege: What is this seething mass of parentheses?!
Master: Your father's Lisp REPL. This is the language of a true
hacker. Not as clumsy or random as C++; a language for a more
civilized age.

-- 
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en

Reply via email to