Friday, January 28, 2011

Non-breaking error handling in Clojure: Part Deux

This post is a continuation from Part One, which talked about how to capture the return value as well as the Exception that a body of code might give back. Here we will examine how to selectively ignore or notice exceptions. These macros below are taken from the Clj-MiscUtil library I am working on. Onto the code now.

Use case #1: Ignore the exception unless a predicate returns true; return nil if an exception is ignored.

(defmacro filter-exception
  "Execute body of code and in case of an exception, ignore it if (pred ex)
  returns false (i.e. rethrow if true) and return nil."
  [pred & body]
  `(try ~@body
     (catch Exception e#
       (when (~pred e#)
         (throw e#)))))

This is useful for cases where one needs arbitrary control over how to determine whether an exception should be re-thrown or ignored. An example use case might be when re-throwing of exception is subject to external condition. Let's see it in action.

(def ^:dynamic *debug-mode* false)

(filter-exception #(and *debug-mode* (instance? FooException %))
  ...)

Use case #2: Specify which exceptions you want noticed and which ones should be ignored.

(defmacro with-exceptions
  "Execute body of code in the context of exceptions to be re-thrown or ignored.
  Args:
    throw-exceptions - List of exceptions that should be re-thrown
    leave-exceptions - List of exceptions that should be suppressed
  Note: 'throw-exceptions' is given preference over 'leave-exceptions'
  Example usage:
    ;; ignore all runtime exceptions except
    ;; IllegalArgumentException and IllegalStateException
    (with-exceptions [IllegalArgumentException IllegalStateException] [RuntimeException]
      ...)"
  [throw-exceptions leave-exceptions & body]
  `(filter-exception (fn [ex#]
                       (cond
                         (some #(instance? % ex#) ~throw-exceptions) true
                         (some #(instance? % ex#) ~leave-exceptions) false
                         :else true))
     ~@body))

This macro is a convenience wrapper over filter-exception for the common scenario where one may like to specify which exceptions to re-throw and which ones to ignore. The usage is quite simple as the docstring says. The first vector of exceptions are the ones that should be re-thrown, and the second that should be ignored.

(with-exceptions [IllegalArgumentException IllegalStateException] [RuntimeException]
  "foo" ; non-effective return value
  (throw (IllegalArgumentException. "dummy")))

In the snippet above, it will re-throw the exception is because it is listed in the first vector.

(with-exceptions [IllegalArgumentException IllegalStateException] [RuntimeException]
  "foo" ; non-effective return value
  (throw (NullPointerException. "dummy")))

In this case the exception will be swallowed. Why? Because NullPointerException is a sub-class of RuntimeException and is hence an instance of RuntimeException too!

Hope you find this discussion useful. Feel free to post your comments. You may like to follow me on Twitter.