clojure.contrib.except enhanced

118 views
Skip to first unread message

Stephen C. Gilardi

unread,
Jun 6, 2009, 10:42:10 AM6/6/09
to clo...@googlegroups.com
I've checked in changes to clojure.contrib.except to allow the
functions it provides to produce exceptions that wrap other
exceptions: they now support "causes".

I believe it's now fully general and will be convenient to use for all
of our exceptional needs.

The attached text file contains a demo session at the repl showing
some of its features.

--Steve

except-demo.txt

Chouser

unread,
Jun 6, 2009, 6:39:37 PM6/6/09
to clo...@googlegroups.com

This looks really easy to use, Stephen, thanks.

Since the time I put the rather more top-heavy error-kit in contrib,
it's become increasingly clear to me that hardly anybody (myself
included) need or use the continue-with and bind-continue features it
provides.

That leaves as its only used feature the ability to define and catch
custom error types that can be created without AOT compilation. But
I'm starting to come around to what I think Rich has been pointing out
all along: that having singly-typed errors is not necessarily a great
idea. Clojure multi-methods don't require objects to have a single
type after all -- you can dispatch on whatever you'd like. Why should
exceptions be any different?

So here's an idea: what if Clojure (or contrib for now) provided
a single new Exception type that besides the normal error string also
carried an object: probably a map with unspecified content (a bit like
metadata is now). Then a lib like contrib.except could make it easy
to toss bits of detail into the exception that could be read easily
when caught. This would allow the simplicity that contrib.except
provides now and would allow you to provide as much detail as an
error-kit error without even having to declare an error type.

Perhaps using it would look something like:

(defn foo [x y]
(if (neg? x)
(throwf :source ::Args, :arg 'x, :value x,
"The value x may not be negative")
(+ x y)))

(try
(foo -5 10)
(catch ClojureError e
(if-not (isa? ::Args (:source e))
(throw e)
(printf "Argument was incorrect:" e))))

I suppose the catch clause could be augmented:

(tryf
(foo -5 10)
(catchf [e] (isa? ::Args (:source e))
(printf "Argument was incorrect:" e)))

...or something.

Any thoughts?
--Chouser

Stephen C. Gilardi

unread,
Jun 9, 2009, 10:57:03 AM6/9/09
to clo...@googlegroups.com

On Jun 6, 2009, at 3:39 PM, Chouser wrote:
> On Sat, Jun 6, 2009 at 10:42 AM, Stephen C.
> Gilardi<sque...@mac.com> wrote:
>> I've checked in changes to clojure.contrib.except to allow the
>> functions it
>> provides to produce exceptions that wrap other exceptions: they now
>> support
>> "causes".
>>
>> I believe it's now fully general and will be convenient to use for
>> all of
>> our exceptional needs.
>>
>> The attached text file contains a demo session at the repl showing
>> some of
>> its features.
>
> This looks really easy to use, Stephen, thanks.

Thanks!

> Since the time I put the rather more top-heavy error-kit in contrib,
> it's become increasingly clear to me that hardly anybody (myself
> included) need or use the continue-with and bind-continue features it
> provides.
>
> That leaves as its only used feature the ability to define and catch
> custom error types that can be created without AOT compilation. But
> I'm starting to come around to what I think Rich has been pointing out
> all along: that having singly-typed errors is not necessarily a great
> idea. Clojure multi-methods don't require objects to have a single
> type after all -- you can dispatch on whatever you'd like. Why should
> exceptions be any different?

That's interesting. I'm sorry I hadn't yet looked at error-kit in
detail. I do like the idea of using more idiomatic Clojure techniques
for communicating error information up the stack.

> So here's an idea: what if Clojure (or contrib for now) provided
> a single new Exception type that besides the normal error string also
> carried an object: probably a map with unspecified content (a bit like
> metadata is now). Then a lib like contrib.except could make it easy
> to toss bits of detail into the exception that could be read easily
> when caught. This would allow the simplicity that contrib.except
> provides now and would allow you to provide as much detail as an
> error-kit error without even having to declare an error type.

That's cool. You mentioned metadata. Perhaps that would be a good way
to implement it. As a prototype to play with, I've enclosed
Condition.java which for now is convenient to place in clojure/src/jvm/
clojure/lang . It's a subclass of Throwable which is (mostly and when
it's done completely) immutable and supports metadata. I've also
enclosed a version of except.clj that include "handler-case" and
"raise" macros That use it to communicate a map from the site of an
error to a handler for the error.

I used Common Lispy names for the functions, but we may or may not
want to keep that given that there are differences in the operations.

> Perhaps using it would look something like:
>
> (defn foo [x y]
> (if (neg? x)
> (throwf :source ::Args, :arg 'x, :value x,
> "The value x may not be negative")
> (+ x y)))
>
> (try
> (foo -5 10)
> (catch ClojureError e
> (if-not (isa? ::Args (:source e))
> (throw e)
> (printf "Argument was incorrect:" e))))
>
> I suppose the catch clause could be augmented:
>
> (tryf
> (foo -5 10)
> (catchf [e] (isa? ::Args (:source e))
> (printf "Argument was incorrect:" e)))

Here's how it looks with this handler-case and raise:

user=> (defn foo [x y]
(if (neg? x)
(raise :source ::Args :arg 'x :value x :message "shouldn't be

negative")
(+ x y)))

#'user/foo
user=> (foo 3 4)
7
user=> (handler-case :source c
(foo -5 10)
(handle ::Args
(printf "Argument was incorrect: %s\n" ^c)))
Argument was incorrect: {:arg x, :message "shouldn't be
negative", :source :user/Args, :value -5}
nil

c also contains the stack trace accessible by (.getStackTrace c).

> Any thoughts?

Could be cool. Back at ya. :)

--Steve

Condition.java
except.clj

Stephen C. Gilardi

unread,
Jun 10, 2009, 3:08:23 AM6/10/09
to clo...@googlegroups.com
Based on the discussion in this thread with Chouser, I've checked in
code for "throwable maps with stack traces" at
clojure.contrib.condition. It's a very generic scaffolding for
flexible error handling. Here's Chouser's example implemented with
clojure.contrib.condition:

-----------------

(ns clojure.contrib.condition.example
(:use clojure.contrib.condition))

(defn func [x y]


(if (neg? x)
(raise :source ::Args :arg 'x :value x
:message "shouldn't be negative")
(+ x y)))

(defn main
[]
(handler-case :source condition
(println (func 3 4))
(println (func -5 10))
(handle ::Args
(printf "Bad argument: %s\n" condition))))

-----------------

Clojure 1.1.0-alpha-SNAPSHOT
user=> (run clojure.contrib.condition.example)
7
Bad argument: {:stack-trace #<StackTraceElement[]
[Ljava.lang.StackTraceElement;@6bf1a3f4>, :arg x, :message "shouldn't
be negative", :source :clojure.contrib.condition.example/Args, :value
-5}
nil
user=>

-----------------

Comments welcome.

--Steve

Reply all
Reply to author
Forward
0 new messages