=> (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