Generic Exceptions: CLJS CLJ and other.

219 views
Skip to first unread message

Dave Sann

unread,
Feb 16, 2013, 7:55:54 PM2/16/13
to clojur...@googlegroups.com
I don't use (m)any exceptions in code that I want to be portable across cljs and clj. The main reason is that while I can work out ways to throw, I have found no way to generically catch them.

I have a number of possible options for how this might be changed. Here are 3 for discussion in order of complexity. This is not intended to address all issues, just be a step in a sensible direction. Matching on exception info might be a future step.

1. The current cljs.core.ExceptionInfo might be renamed or aliased clojure.lang.ExceptionInfo

Rational: ExceptionInfo could be considered a "core clojure type" implemented in both cljs and clj to have the similar usage/behaviour. Therefore using a clojure specific rather than cljs specific name might be appropriate. (this is similar to having clojure.string, not cljs.string).

This would allow the writing of

(try ...
(catch clojure.lang.ExceptionInfo e ...))

Note that the full name is used. This avoids having :import in the generic file (due to differences with cljs in this respect).

2. paper over the above, adding to try catch syntax, as in:

(try ...
(catch-info e ...))

3. maybe extend out something like slingshot (https://github.com/scgilardi/slingshot) to work with cljs and effectively replace core try catch.


thoughts?

Dave


Brandon Bloom

unread,
Feb 17, 2013, 9:43:12 AM2/17/13
to clojur...@googlegroups.com
Both throwing and catching exceptions are incompatible between Clojure and ClojureScript because, as you point out, the types don't match. This isn't just true of ExceptionInfo, it's also true of IllegalArgumentException and ValueError, for example.

Luckily, idiomatic Clojure code doesn't throw exceptions very much. I tend to view the `throw and 'catch forms as interop forms, but `try and 'finally are idiomatically useful for cleaning up after errors even if you never explicitly throw or catch in your entire program.

RE #1: There is already some precedent for namespace rewriting in ClojureScript, but I'm not sure how I feel about it. I think that we should examine the pros, cons, and alternatives to namespace rewriting more broadly before more deeply considering the case of exception handling. In particular, I'm worried that the further down this road we go, the more likely we are to amass a vast collection of complex name aliases. I don't want to be in that business.

RE #2: I think that catch-info ignores the rest of the gambit of incompatible exception types. It would be prohibitively complex to try to group together all of the types into a common hierarchy or aliases across hosts. However, it may be tractable to provide a single catch-all type construct that would match (catch Throwable ...) on the JVM and the normal catch semantics of JavaScript. You could then use a clojure.core/ex-info? predicate or create your own for the host-specific errors you need to test for generically between platforms.

RE #3: I don't know much about slingshot, but the idea of wrapping up the host/interop exception mechanism has merit, but needs much more thought. At minimum, it would be nice to have a `raise macro which would take a string, an ex-info map, an Exception or Error, etc, to handle the extremely common cases of (throw (Exception. "abcd")) or (throw (Error. "asdf")) breaking portability. Such a raise macro would be the spiritual mirror of a catch-all form.

Dave Sann

unread,
Feb 17, 2013, 6:11:24 PM2/17/13
to clojur...@googlegroups.com
Hi Brandon,

You have taken the extent of my ambitions further than I expected. I am only thinking of ExceptionInfo. I agree there is no point in trying to match Exception type hierarchies across platforms.

Today, I don't think there is any way to write generic code that can throw and catch ExceptionInfo. This is the base ability I would like to have.

ExceptionInfo is common to both clj and cljs. it is effectively just a message and data. I think, making this syntactically the same across clj and cljs would be useful.

in practical terms, The following should work with both clj and cljs

(throw (ex-info "oops" {:stuff ..."})

(try (things) (catch clojure.lang.ExceptionInfo e (do things))

or

(throw (ex-info "oops" {:stuff ..."})

(try (things) (catch-info e (do things))

Is this clearer? It should be achievable.


Any questions of interop are different and would require wrapping of exception types at a platform specific level. I haven't thought about this in detail.

Slingshot is interesting because it allows matching on thrown data.

> "idiomatic Clojure code doesn't throw exceptions very much".

This is interesting - I don't have a firm view. It is true that I have found that I rarely need to throw exceptions. But exceptions (and restarts from lisp) are both useful control structures. I think that the explicit type hierarchy with java exceptions is extremely clunky but this doesn't mean that exceptions should not be part of idiomatic Clojure code.


Dave

Brandon Bloom

unread,
Feb 17, 2013, 9:39:04 PM2/17/13
to clojur...@googlegroups.com
> Today, I don't think there is any way to write generic code that can THROW [...] ExceptionInfo.

Doesn't (throw (ex-info "msg" {:data "xyz"})) cover that?

> Today, I don't think there is any way to write generic code that can [...] CATCH ExceptionInfo.

You are correct. However, (ex-data (Exception. "asdf")) cleanly returns nil. So if you had catch-all, you could trivially test for ExceptionInfo:

(catch-all e
(if-let [data (ex-data e)] ...))

> ExceptionInfo is common to both clj and cljs. it is effectively just a message and data. I think, making this syntactically the same across clj and cljs would be useful.

Agreed, but I think simultaneously making catch-all semantically the same would be better.

> Any questions of interop are different and would require wrapping of exception types at a platform specific level. I haven't thought about this in detail.

> Slingshot is interesting because it allows matching on thrown data.

I think this is precisely why it's not a great idea. Clojure doesn't have pattern matching built in, it has destructuring, which is non-conditional. It would be rather odd to have pattern matching built in to catch, but no where else. We could allow destructuring in the catch-all if the catch-all returned a map that was bigger than just the payload of the ExceptionInfo. For example, if there were a :type and :stack key, you could:

(catch-all {:keys [type stack] :as e} ...)

However, this might not jive with existing usages of ex-info which likely use un-namespaced keys.



> > "idiomatic Clojure code doesn't throw exceptions very much".
>
> This is interesting - I don't have a firm view. It is true that I have found that I rarely need to throw exceptions. But exceptions (and restarts from lisp) are both useful control structures. I think that the explicit type hierarchy with java exceptions is extremely clunky but this doesn't mean that exceptions should not be part of idiomatic Clojure code.

Clojure does not provide restarts and, without continuations, can't do so meaningfully. Additionally, anything that would leverage exceptions to implement non local control flow would likely be platform specific in terms of semantics.

Exceptions are useful for unrolling the stack and protecting sub-processes, but not much else. If you have a non-exceptional error, use a return value. If you have a sub-process that should have independent failure, catch all exceptions and handle accordingly. If you have a badly written library that uses exceptions for control flow, that's generally platform specific library with platform specific type names and that's an interop situation. Wrap those functions and return meaningful result codes.

Match-by-type is a bad idea inherited from the early days of misguided Java libraries that use a giant pile of purpose built exception classes for questionable static safety guarantees. It's convenient for Java interop, but it's not really a good idea in general and it's unfortunate that 'catch exposes Java's semantics to ClojureScript.

Dave Sann

unread,
Feb 17, 2013, 11:50:13 PM2/17/13
to clojur...@googlegroups.com
I think that we are off track from my main objective so I won't comment on many of the points that you have raised. They are worthy of discussion but not my focus.

My focus:
I think that there is an opportunity to enable the basic use of ExceptionInfo as a way to use exceptions in code that will work with clj, cljs (and other). I think this is missing and it does not seem to much of a stretch to achieve.

The main point:
I have suggested some alternatives, but I think option 1 is a simple way to get the desired outcome in the near term. Is it an acceptable change? Is there anything grossly wrong with renaming/aliasing cljs.core.ExceptionInfo as clojure.lang.ExceptionInfo?


More detail:

Premises:
1. Exceptions are supported by both clojure and clojurescript. They have their uses.
2. It is a good idea to be able to write syntactically generic code for handling exceptions that is portable. This is limited to the case of ExceptionInfo (just data, not platform types).

Current Situation:
1. you can throw generically using ex-info.
2. you can't catch generically.
3. catching is not something that you can easily factor out and so not being able to do this precludes the use of exceptions in portable/generic code.

Options suggested:
Option 1: is a very simple way to make this work and only requires a small change to ClojureScript.
Option 2: is an example of a slightly more involved way to do the same thing. Involves change to ClojureScript and Clojure.
Option 3: is more involved still. It requires more discussion and should probably be considered a future topic for extended debate.

Other:
I have some immature thoughts on why matching for exceptions may be useful in preference to de-structuring in the case of exceptions. But it is a digression in this context.

Cheers

Dave

Dave Sann

unread,
Feb 18, 2013, 7:57:58 AM2/18/13
to clojur...@googlegroups.com
I had a look at the implications of renaming and it didn't work out ideally. Ask if you are interested why.

However as a 4th option. ExceptionInfo appears to be imported by default into Clojurescript. If this were the same in clojure, you could then write:

(throw (ex-info "oops" {:stuff ..."})

(try (things) (catch ExceptionInfo e (do things))

which is maybe better and was raised in this thread: https://groups.google.com/d/topic/clojure/FCe__ITsgtw/discussion

Dave

Brandon Bloom

unread,
Feb 19, 2013, 5:23:57 PM2/19/13
to clojur...@googlegroups.com
> However as a 4th option. ExceptionInfo appears to be imported by default into Clojurescript. If this were the same in clojure, you could then write:

There are a whole bunch of things in clojure.lang that are not imported by default. ClojureScript doesn't have a clojurescript.lang namespace, instead cljs just sticks everything in core. I'm not necessarily convinced that ExceptionInfo is special enough to justify handling it differently from PersistentHashMap, etc. Namespace defaults is a whole other topic to be discussed for clj/clj interop.

In the meantime, you can (import 'clojure.lang.ExceptionInfo) or add the analogous to your ns form.

Dave Sann

unread,
Feb 19, 2013, 6:01:48 PM2/19/13
to clojur...@googlegroups.com
Yes, I can do this. But then I must either maintain 2 copies of the file, one for clj and one for cljs or have some way of importing only in the JVM case. This is the point.

But look, apart for our discussion - this seems to have limited interest amongst others. as was pointed out on another thread a theme of clojure 1.6 is to look compatibility and sharing code. And I expect this will be much more thorough than my current considerations.

I have avoided exceptions in generic code this far. I can continue to do so for now.

Dave

Reply all
Reply to author
Forward
0 new messages