-
Notifications
You must be signed in to change notification settings - Fork 15
Exception Handling
Exception handling is fairly similar in structure in Java and JavaScript, with one notable exception: a Java catch block matches on a type, and a JavaScript one does not. How should we resolve this in the Clojure API?
Options
- catch in ClojureScript simply has one less argument than catch in Java (catch is about platform interop, and platform interop forms can and will differ)
- RH - try/catch is not only about interop - it is the only error handling mechanism in Clojure
- SDH - for me, ClojureScript is forcing the issue of whether this should continue to be the case
- catch in ClojureScript has a dummy arg that is ignored
- catch in ClojureScript tries to simulate the semantics provided in Javaland
- ClojureScript uses a different name than 'catch' so that people aren't caught off guard by the different API and semantics
I think #1 wins, and have already implemented it. Reasons:
- Platform interop will be different and look different where the semantics are different. We have already seen this with the dot (.) form.
- catch is clearly an interop form, not a manifestation of the Clojure Way to deal out-of-band with errors. The latter would be dynamic binding of a handler, which has two big advantages over exceptions:
- action at the point of failure
- composability with all our other abstractions to enable flexibility (in particular protocols and multimethods)
- RH - No handler system stands alone independent of a non-local transfer of control mechanism (i.e. what do you do if no handler is registered, or the handler would like you to abort?). And the "Clojure Way" you describe is an idea with few extant implementations, certainly none in core.
- If we try to fake the Java syntax or semantics, we are just encouraging people to do the wrong thing (i.e. conditionalizing error handling with types). It would suck to see people adding this to their JavaScript world because we encouraged it.
- RH - I'm not sure we are encouraging anything, any more than having atoms is encouraging people to use them as local variables
- SDH - I don't but this at all. Local variables have clear alternatives in Clojure, but the exception handling approach we are discussing would be the only game in town.
- RH - different 'types' of exceptions are already thrown by JS. To the extent people are already building conditionals around those types, this provides a simpler mechanism. People who don't care can just catch Error.
- SDH - do we have some examples of people needing/using this conditionality?
Admittedly, approach #1 will cause pain for people who are porting Clojure libs to ClojureScript. Every place that catches a Java exception will be broken, and have to be handled differently in ClojureScript. These break down into two cases:
- Places where the Java exception handling cares about the type of the exception. In this scenario you are screwed anyway, no matter what we do. The JavaScript side will already be different, and papering it over in the catch syntax won't prevent the need for separate code.
- RH - Eventually there will have to be another answer besides 'you're screwed' in this area where the types simply must differ, as well as other areas; i.e. conditional compilation or such. If you encapsulate the types you are going to have to unify access to the error information they contain. That would be hard enough to do on the Java side, where there are already some standards. People can throw random things in JS.
- Places where the Java exception handling does not (or should not) care about the exception type. If there are a ton of these, and people's code could be the same across Clojure and ClojureScript except for the catch block syntax, I would recommend offering a new "catchall" form. Where catch is for platform interop, catchall is the Clojure way. catchall would catch all exceptions that application code should ever catch, and would provide no way to match on type. It would map to (catch) in ClojureScript, and to (catch Exception) in Clojure.
- RH - And after you've caught-all an 'opaque blob' what do you do with it?
- SDH - abort some operation short of killing the toplevel, which is most of what I see people doing.
- RH - If Clojure had a standard exception 'type', it could be present in both. Throwing and catching that could be portable. Getting information out of it would be portable. I don't see how you get out of being able to distinguish what was thrown though, if you can't force everything to use it.
- RH - The 'problem' of exception type proliferation is not due to having a catch that can distinguish, but to the failure to provide an extensible, data-conveying exception type in the first place, and the convention of using types to convey data. Given an extensible data-conveying type you can avoid that problem by simply using it. However, it doesn't save you from having to deal with errors generated by people with a different approach.
- SDH - OK.
It is worth noting that while the goog library's static typing appears to support verifying exception types at the point of a throw, but they have made no effort I can see to branch on type at the point of catch. If they don't think this is good/doable, we probably shouldn't either. * RH - Conventions work in a single shop. * RH - Are we making up a new language and VM here? If so, this kind of thinking would be on the table, and I might agree. But we are supposed to be subsetting, and this seems like a rather unimportant place to introduce an incompatibility. * SDH - Isn't it a breaking change for existing libs no matter what? JavaScript doesn't have e.g. IOException. * RH - I am not advocating trying to make existing catches work. There will have to be some mechanism (eventually, not now) for isolating host-specific types. That mechanism can't be a neutering of catch to something unsuitable for the tasks at hand (and the subsequent reinvention over and over of exception type dispatch) Consenting adults and all that. Let's get DataConveyingException (with a short name please) into Clojure and ClojureScript and people can throw it and catch it and be happy, without being stuck with an inadequate catch.
I didn't give much consideration to option 4, using a different name than "catch". The name has too much good connotation, and we have already set the pattern (with the dot operator) of using the existing names and letting their meanings be slightly different. * RH - I don't understand this sentence. dot is an interop form, designed to capture as much of what dot means as makes sense for the platform. The platforms are slightly different. * SDH - (edited sentence to make sense, maybe).
RH - I'm unconvinced by your argument, in particular the lack of identification of any deep problems with option #3 and portability approach #3
SDH - If we move forward with option #3, is the type matching to be implemented by chasing up the prototype chain?
RH - Just use instance? in a cond(p). There is no useful 'any' type in JS, and we'll need to deal with strings and numbers that are not Strings and Numbers. That is true elsewhere as well.