General musings on programming languages, and Java.

Monday, May 28, 2007

Optional Checked Exceptions - Spitting at the compiler

In further anecdotal evidence that Gilad was right, Neal Gafter has blogged with a suggestion that checked exceptions could be removed from Java. He's right, they could. As far as I can see, all existing code would still compile, and what's more, it would actually work with existing bytecode with no problem, as the runtime has no problem with throwing undeclared checked exceptions. I've thought for a while that the problem with checked exceptions is that the API that throws them doesn't know whether you will care to deal with them.

First, I'll mention some ways in which closures can help with checked exceptions, then a mechanism for optional checked exceptions:

Reader.close() can throw an IOException, but we don't let it propagate. Closures to the rescue:
suppressing(IOException.class){ reader.close();}

Making Swing use the native look and feel requires catching about 5 exceptions, but we don't care about any of them:

loggingAndIgnoring(Exception.class){ UIManager.setLookAndFeel..}

Or even better:

whenExceptionsHappen({e => e.printStackTrace();}){ UIManager.setLookAndFeel...}

Suppose that we do want to propagate the exception instead, but we don't want to wrap it in a RuntimeException:

public void readsFromAFile() spits IOException

I'm not seriously suggesting 'spits' as a keyword, feel free to think of your own (I also like 'farts' and 'vomits', but that's because I've had a very busy weekend), but in this context, a method that spits IOException is declaring that it can throw an IOException, but that callers of it do not have to catch the IOException. That way everyone can be reasonably happy. API designers can still declare their exceptions using throws, guaranteeing that the client programmer has to think about the exception, but the client programmer can prevent the exception from leaking through their codebase, by using spits.

There is one problem - catching a checked exception from code that does not throw that exception is currently a compile-time error. Spat exceptions would make that compile-time error need to disappear too.

Challenge: think of a better keyword than 'spits' for this.

14 comments:

Neal Gafter said...

Would you care to show the implementation of suppressing, please?

Ricky Clarkson said...

public <E extends Exception,throws X> void suppressing(Class<E> clazz,{=>void throws X} block)
{
. . try
. . {
. . . . block.invoke();
. . }
. . catch (Exception e)
. . {
. . . . if (!clazz.isInstance(e))
. . . . . . Unsafe.getUnsafe()
. . . . . . . . .throwException(e);
. . }
}

Jonathan O'Connor, Ireland said...

How about "lobs" instead of "spits"? Its a gentle throw!

Matthew said...

Maybe we could use "tosses" as a compromise between "throws" and "vomits" :)

Christopher said...

May I propose the word "avail" or "avails"

Here's a definition for the lazy:

avail: to be of use or value to;

public void doesFoo(String bah)
throws FehException
avails MehException

Anonymous said...

I hope your busy weekend didn't involve too much 'farts', vomits' or 'spits'... :)

Ricky Clarkson said...

I ate two fish-based meals with one of those pretend vegetarians, then didn't really feel so good. My weekend involved one of those verbs, yes.

Amazingly, none of these comments have called this a bad idea, which for my blog, is quite unusual.

Stefan Schulz said...

Maybe noone opposed, because everyone is busy over at Neal's or Rusty's flaming on dropping checked exceptions ;)
Which of your ideas, anyway? The one to "wrap" exceptions using control invocation? I'm not sure, if this gives much of a gain wrt. using try catch directy. And, as we do not have closures yet, it's no applicable solution.
Btw., it would be much nicer to have VarArgs here, to allow for an arbitrary number of exceptions to be passed. Not sure, why you need generics for defining the exception type as parameter.
Spitting, well, what would it mean? If not handled directly, the checked exception will be handled as unchecked exception further on? I know, the compiler already does that anyway, but you would get in trouble catching that exception further up, where no throws (or spit) statement exists on that exception, i.e., unreachable code from the compiler's perspective.

Stefan Schulz said...

Missed your last paragraph that already states the issue of unthrown exceptions. The trouble is difficult to solve, though, as the compiler would not know, if a spitting method that is down the call chain will be called at all. Hence, I think it is cleaner to use unchecked exceptions or change the semantics of checked exceptions in general rather than introducing a spitting keyword.

Ricky Clarkson said...

Stefan, the main idea here is 'spits', the rest is probably misplaced.

Your comment about varargs - the 'throws X' generic type parameter works like varargs, as far as I can discern - X can be any number of exception types. I don't think you can catch X, though perhaps reification would make that possible.

I have asked Neal about varargs for generics in general, using currying as an example, but he would rather implement a specific currying operator than varargs for generics. I didn't provide another example.

I know of a truly dodgy way of pretending to have vararg generics in Java 5/6 - http://cime.net/~ricky/tmp/vargenerics.txt

To clarify on your question about spitting - a spat exception is declared in the method header, but is passed on to the caller without the compiler checking anything. There is no conversion or wrapping from one exception type to another going on.

The trouble with reachability analysis for catch blocks is the same whether you consider Neal's proposal or mine.

Stefan Schulz said...

With varargs I was refering to your "suppressing" code. Unfortunately, a closure has to be the last parameter for using control invocation in the current proposal. If it took vararg exception classes, one could easily loop until a matching class is found or all classes have been checked.

The problem with catching undeclared checked exceptions would simply not appear when generally changing the semantics for checked exceptions. With spitting, you introduce a third way of handling exceptions, which may be rather confusing.
I still think, changing uncought checked exception to raise a warning instead of an error could work quite well with no further change in the language or VM itself. Maybe, one should enforce handling unchecked exceptions when throwing them, though, to make people declare them.

Ricky Clarkson said...

There are workarounds for the restriction on control invocation - e.g.,:

forAll(one,two,three).invoke(s:)
{
. . operateOn(s);
}

I'm not sure I would advocate that, but it's definitely possible.

I see what you mean now - you're saying that it still makes sense to have a compiler error if you catch an undeclared checked exception. I agree. I'll revise my idea, to now say that catching an undeclared checked exception implies too much knowledge of implementation details, and is thus undesirable. Some people would still achieve it, by catching Exception and using instanceof, so it's worth providing a way of doing it.

Another way would be a special catch form that allows spat exceptions, bypassing the checker.

I'm fairly ambivalent on which I prefer. In an ideal world, I prefer the first, because it encourages good behaviour, by not relying on the internals that the API designer was trying to hide. However, in this world, some people will circumvent that restriction, and for good reasons at least sometimes. So I'll plump for the second, and unify the syntax, using 'private' to mean thrown but not part of the API (copying C++'s private inheritance, pretty much).

public void method() private throws IOException;

try
{
. . method();
}
catch (private IOException exception)
{
. . exception.printStackTrace();
}

..where catching a private exception would be generally frowned upon, but provided because the workaround for not providing it is worse than providing it.

I'm not going to spend any great effort defending this idea, as I don't actually use exceptions very much anyway. If anyone else wants to pick it up and run with it, I'll support them.

Anonymous said...

I would suggest chuck or pitch. Both are like throws, but also have connotations of throwing away. Of the two, chuck may be better because chucks adds an "s", whereas pitches adds an "es".

Reinier Zwitserloot said...

I suggested this exact same thing a while ago.

I called it 'ignores', and I was debating on whether to use "ignores" or @Ignores. @Ignores is probably a bad idea but it avoids any 'assert' fiascos.

I've been propagating this idea for about a year now and for some reason I keep getting flamed about the utility of checked exceptions. Sure, they're useful. That's why you have to explicitly ignore them, duh. At any rate, I haven't yet heard a solid argument AGAINST optional explicit ignoring of exceptions. The only problem is that it complicates the languages just a little, but for checked exceptions, everyone's favourite issue to disagree on, it surely seems worth the minor complication.

NB: an ignored exception does not get rewrapped as a RuntimeException, it just gets 'silently' thrown, same way you can silent-throw with Class.newInstance and Thread.kill(Throwable), but I think your spits also works like that. Just to clarify.

Blog Archive

About Me

A salsa dancing, DJing programmer from Manchester, England.