Exception transparency - lone throws (no checked exceptions)
Stephen Colebourne
scolebourne at joda.org
Thu Jun 10 05:57:47 PDT 2010
I wanted to write up this possible alternative to exception
transparency - "lone throws". This is a relatively informal
description for now to express the concept.
Proposal
--------------------
This is a simple proposal to permit a new form of throws clause on a
method definition. The syntax is "throws" with no exception type
("lone throws"). For example:
interface Block<T> {
public void invoke(T element) throws;
}
A method using this syntax is permitted to throw any Throwable without
declaring which explicitly:
class MyBlock implements Block<String> {
public void invoke(String element) throws {
new File(element).getCanonicalFile().delete();
}
}
In the example, the code can throw IOException, but we don't declare
it in the method signature - just the fact that the method can throw
anything (checked or unchecked).
On the receiving side, the compiler rules for exception catching are
changed such that if the total set of exception types that a block of
code can throw includes "lone throws", then the compiler will permit
any checked exception to be caught in the catch block:
try {
MyBlock b = new MyBlock();
b.invoke("C:\README.txt");
} catch (IOException ex) {
...
}
In the example, the IOException can be caught, because the invoke
method declares "lone throws" (normally it can't be caught unless the
block declares it actually throws IOException)
Extending this to lambdas, I propose that all function types
implicitly specify "lone throws":
#void(String)
This example is equivalent to the Block interface above - the "lone
throws" is implied.
Here is the forEach example using such a function type:
public <T> forEach(#void(T) block) throws;
Any exception thrown by the passed-in block can flow-out of the forEach method.
Comparisons:
--------------------
Strawman:
#void(String) throws IOException
public <T, throws E> forEach(#void(T) throws E block) throws E;
interface Block<T, throws E> { public void invoke(T element) throws E; }
public <T, throws E> forEach(Block<T, throws E> block) throws E;
Lone throws:
#void(String)
public <T> forEach(#void(T) block) throws;
interface Block<T> { public void invoke(T element) throws; }
public <T> forEach(Block<T> block) throws;
Bytecode:
--------------------
Since checked exceptions are a compile-level construct, there are no
byte-code changes for standard method code. However, there is an open
question as to what the method signature should be in bytecode.
Options include:
a) declare it as Throwable
b) declare it as a new supertype of Throwable
c) add a marker flag that indicates it can throw the special any
exception concept
d) don't add anything to the signature (however, it would then not be
possible to catch checked exceptions thrown by "lone throws")
Comment:
--------------------
I believe that this approach provides exception transparency.
I believe that this approach is easy to specify and implement. (No
disjunction types or interactions with generics that I can see)
I believe that this approach is easy to learn.
This approach results in a lot simpler syntax for function types, as
there is no longer a need to declare exceptions. This may open up new
syntax choices.
This approach results in a lot simpler syntax for exception
transparent methods. There is no "follow this pattern of boilerplate
code with throws E twice in generics and also in the throws clause" -
instead, you just add one keyword.
Current checked exceptions are maintained, however users have the
choice to effectively turn them off. Given many APIs already do this
today (by wrapping checked exceptions in unchecked) this would
probably be used beyond just lambdas. (Checked exceptions remain a
religious issue for some - this change offers teams a choice as to how
they are handled.)
Reinier's sort/TreeSet example is fine and simpler (ignoring the fact
that Set can't be changed in a backwards incompatible way):
Set:
public boolean add(T t) throws;
Collections:
public static <T> void sort(List<T> a, #int(T, T) c) throws { ... }
The approach is less powerful. Some type-information is lost, and it
is not possible to write exception filtering lambdas that remain
exception-type-safe. My opinion is that these are edge cases.
My opinion is that it is easy to get caught up in additional
type-information and type-safety, but Java is so verbose already that
the impact is necessarily negative. By choosing to reduce the level of
type-information in the system on this occasion, the end-result is a
whole lot simpler to understand, and more in tune with how many larger
open source projects use exceptions today (ie. wrapping any checked
exceptions).
Options:
--------------------
One option is to make every method that receives a function type as a
parameter be a "lone throws" method automatically. There is no
backwards compatibilty issue, as such methods don't exist today. This
option has the advantage that developers don't need to know anything
about exception transparency wrt lambdas. This feels like an
unecessary extra rule to learn however.
Another option is to allow "lone throws" at the class level. This
would set all methods in the class to be "lone throws", essentially
providing a quick way to alter checked exception behaviour for those
teams that choose. I suspect this would be popular.
There may be a role for a catch clause that doesn't specify the
exception type. This would effectively 'remove' the "lone throws" from
the set of exceptions thrown by the block, leaving just the exceptions
declared in standard method signatures. This would provide the ability
to wrap these in a RuntimeException or similar if desired. This needs
more study to determine its usefulness.
Stephen
More information about the lambda-dev
mailing list