Enhanced throw and catch for Clojure
throw+. Each is 100% compatible with Clojure
and Java's native
throw both in source code and at
runtime. Each also provides new capabilities intended to improve
ease of use by leveraging Clojure's features like maps, records, and
throw behave much like those of Java:
throw can accept objects derived from java.lang.Throwable and
selects from among catch clauses based on the class of the thrown
In addition to fully supporting those uses (whether they originate
from Clojure code or from Java code via interop),
throw+ provide these enhanced capabilities:
throw+can throw any Java object, not just those whose class is derived from
Clojure maps or records become an easy way to represent custom exceptions without requiring
try+can catch any Java object thrown by
throw, or Java's
throw. The first catch clause whose selector matches the thrown object will execute.
a selector can be:
a class name: (e.g.,
my.clojure.record), matches any instance of that class, or
a key-value pair: (two element vector), matches objects where
(get object key)returns
a predicate: (function of one argument like
set?), matches any Object for which the predicate returns a truthy value, or
a selector form: a form containing one or more instances of
%to be replaced by the thrown object, matches any object for which the form evaluates to truthy.
the class name, key-value pair, and predicate selectors are shorthand for these selector forms:
`<class name> => (instance? <class name> %)` `[<key> <val>] => (= (get % <key>) <val>)` `<predicate> => (<predicate> %)`
the binding to the caught exception in a catch clause is not required to be a simple symbol. It is subject to destructuring so the body of the catch clause can use the contents of a thrown collection easily.
in a catch clause, the context at the throw site is accessible via the hidden argument
&throw-contextis a map containing:
for all caught objects:
:object the thrown object; :stack-trace the stack trace;
for Throwable caught objects:
:message the message, from .getMessage; :cause the cause, from .getCause;
for non-Throwable caught objects:
:message the message, from the optional argument to throw+; :cause the cause, captured by throw+, see below; :wrapper the outermost Throwable wrapper of the caught object, see below; :environment a map of locals visible at the throw+ site: symbols mapped to their bound values.
To throw a non-
throw+wraps it with a
Throwableobject of class
Stonemay in turn end up wrapped by other exceptions (e.g., instances of
try+sees through all such wrappers to find the object wrapped by the first instance of
Stonein the outermost wrapper's cause chain. If needed, the outermost wrapper is available within a catch clause a via the
&throw-context. Any nested wrappers are accessible via its cause chain.
throw+throws a non-
Throwableobject from within a
try+catch clause, the outermost wrapper of the caught object being processed is captured as the cause of the new throw+.
See the tests for examples
Based on clojure.contrib.condition, data-conveying-exception, discussions on the clojure mailing list and wiki and discussions and implementations by Steve Gilardi, Phil Hagelberg, and Kevin Downey.
Copyright © 2011 Stephen C. Gilardi, Kevin Downey, and Phil Hagelberg
Distributed under the Eclipse Public License, the same as Clojure.