Exception transparency
Brian Goetz
brian.goetz at oracle.com
Mon Jun 7 15:31:32 PDT 2010
[ Also posted to
http://blogs.sun.com/briangoetz/entry/exception_transparency_in_java ]
One of the features being considered under Project Lambda is
*exception transparency*. While this feature is not specifically
required for adding lambda expressions to the Java language, it
increases the expressive power of generic libraries that use closures.
## Problem statement
One of the weaknesses of generics in Java is the treatment of checked
exceptions. Generics provide reasonable power at abstracting over
method return types and argument types, but they do not do a very good
job when abstracting over the types of checked exceptions that can be
thrown by a method. The result of this has been that few libraries
use generic exceptions. Instead, libraries which take callback
objects tend to move towards one of two extremes, illustrated by
Runnable (which throws nothing) and Callable (which throws
everything):
public interface Runnable {
public void run();
}
public interface Callable<V> {
V call() throws Exception;
}
Both of these extremes are undesirable. With Runnable, one must go
through lengths to wrap the exception in an unchecked exception or
invent an alternate means of exposing the exception to the initiator
(see ExecutionException in java.util.concurrent). With Callable, one
must catch Exception regardless of what the block throws, which is bad
in two ways: unneeded boilerplate coding when the block does not
throw, and encouraging users to catch Exception rather than a more
targeted exception type.
Adding closures to the language exacerbates this problem, as more
libraries will want to take block-like constructs and execute them.
The core of the problem is that generic type parameters are *monadic*
[1]; a formal type parameter E of a generic type must represent
exactly one type. But throws clauses are *variadic*; they can contain
zero or more types. So while it is possible to generify over thrown
exception types, one can only do so if one is willing to commit to the
adicity of the throws clause, which is effectively useless for
modeling arbitrary callbacks:
// This is pretty much useless!
public interface ExceptionalCallable<V, E extends Exception> {
V call() throws E;
}
## Variadic type parameters
One solution to this problem is to extend generics to allow a
restricted form of variadic type parameters, which can represent zero
or more types with a common upper bound. There are few places where
variable-length lists of types are permitted in the Java language; one
is the throws clause of a method, and another is (or will be, being
added under Project Coin) is the catch clause of a try block.
A throws type parameter is a generic type parameter that is introduced
with the keyword throws; throws type parameters implicitly have an
upper bound of Exception (though any upper bound that is a subtype of
Throwable may be explicitly specified) and can correspond to zero or
more actual types that are subtypes of the upper bound.
Here is an example of how this might look:
interface Block<T, throws E> {
public void invoke(T element) throws E;
}
interface NewCollection<T> {
public<throws E> forEach(Block<T, throws E> block) throws E;
}
Here, Block is a generic interface whose type signature includes a
throws type parameter E. The forEach method in NewCollection is a
generic method, where the type parameter E of block is inferred from
its argument, and forEach is declared to rethrow exactly the
exceptions thrown by the block argument.
Note that it is perfectly possible that a generic method or class
might have more than one throws type parameter:
<T, throws X, throws Y>
T executeOne(Block<T, throws X> first,
Block<T, throws Y> second)
throws X, Y {
if (randomEvent())
first.invoke();
else
second.invoke();
}
## Exception transparency
With the throws type parameter on the Block interface, we can now
accurately generify over the set of exceptions thrown by the Block;
with the generic forEach method, we can mirror the exception behavior
of the block in forEach(). This is called exception transparency
because now the exception behavior of forEach can match the exception
behavior of its block argument. Exception transparency simplifies the
construction of library classes that implement idioms like internal
iteration of data structures, because it is common that methods that
accept function-valued arguments will invoke those functions, meaning
that the library method will throw a superset of the exceptions thrown
by its function-valued arguments.
## Details and open issues
A throws type parameter is declared in the formal parameter list of a
generic class or interface or of a generic method, is introduced by
the keyword throws, and optionally may have an explicit upper bound
(which must be a subtype of Throwable). If the upper bound is
omitted, it is assumed to be Exception.
A throws type parameter may appear in the actual parameter list for a
generic variable declaration (only in a position corresponding to a
throws parameter), in a throws clause of a method, or in the formal
argument of a catch block. Where it appears in the catch block, it is
treated by the compiler as a catch of its erasure.
It is expected that most of the time, throws parameters will be
inferred by the compiler rather than explicitly stated, such as when
SAM-converting a lambda expression to a SAM interface type such as
Block in our example.
A syntax is required to indicate that an actual type parameter for a
throws type variable is niladic. In BGGA this was "throws
Nothing"; we will likely use "throws void" or "throws Void".
We may wish to not require the throws keyword at the use site;
Block<E> instead of Block<throws E>.
[1] Monadic in the sense of fixed adicity (arity), not in the sense of
category theory or functional programming.
More information about the lambda-dev
mailing list