Exception transparency
Neal Gafter
neal at gafter.com
Mon Jun 7 16:45:27 PDT 2010
You describe a syntax for declaring these new kinds of type parameters, but
no syntax for the use site. The one-type case is familiar, and you make two
suggestions for the no-type case, but what about the two-or-more case (e.g.
"IOException or SQLException")? What about disjunctive bounds (they arise
in APIs once you start using these type parameters).
I conjecture that a new syntax may not be needed at the declaration site at
all; it can be inferred from the explicit bound on the type parameter
instead of vice-versa, simplifying the syntax and making it more familiar.
Specifically, you can treat any type parameter as a throws type parameter if
its bound is Throwable or a subtype. There is no need to restrict where the
type parameter may be used.
I conjecture that a new syntax may not be needed at the use site either, or
at least not frequently. Explicit type parameters in an invocation always
appear after a kitten-killing ".<", which cannot be confused with a
less-than operator. In a function type, similarly, they appear where an
expression cannot. When these are actual type parameters of an interface
type, wildcards are most often needed at the use site to get proper
subtyping, and that is enough of a syntactic hint that disjunctive types
using "|" cannot be confused with a logical-or operator. In the base clause
of a class or interface declaration, and in an object creation expression
they are contextually unambiguous. Only cast expressions are syntactically
problematic, and those may be disambiguated by looking ahead to the
close-paren.
If these conjectures turn out to be true, you may be able to do away with
any special syntax in most code, with the exception of whatever syntax is
needed to bound and/or separate the multiple types of the type parameters (I
recommend "|").
How do these new type parameters interact with wildcards? Can they be used
in function types? What are the subtyping rules?
You suggest that inference will frequently provide these type parameters,
but there is no provision for that in any of the draft specifications for
project lambda. Type inference works based on subtype conversions, but not
the lambda conversion, and it cannot infer anything other than a single type
for a type parameter. Presumably this will be done with some new extensions
to the type inference specification, as in BGGA. I'm very interested in how
that might look in the context of project lambda. Has Maurizio gotten far
enough on this that he might share something?
Your suggestion that a type parameter may be used in a catch blocks and is
treated as the erasure will be very confusing, and depending on precisely
what you mean it might undermine exception checking. Can you please explain
what motivated you to add that? Are you hoping that the interaction with
final type parameters may allow intercepting exceptions even when the
exception type is a formal generic parameter?
On Mon, Jun 7, 2010 at 3:31 PM, Brian Goetz <brian.goetz at oracle.com> wrote:
> [ 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