[promoted to amber-spec-experts]
I think we should tackle this problem at its core and retire the notion of
checked exceptions
The written are on the wall since quite some time, but we have not yet
acknowledge it.
Let's recap the issue and the clues pointing in that direction.
I believe the first straw can be tracked back to the inability of the type
system to create a type variable containing the union of exceptions when
generics where introduced in Java 5.
It's the reason why there is no ThrowableConsumer, as an example, imagine that
a stream can declare a checked exception Stream<T, E> and a user call map()
that takes a ThrowableFunction,
we need a way to express that the resulting stream is that combine the
exceptions of the original stream and the exceptions that may be raised by
calling the function of map.
Stream<T, E extends Exception> --> map(ThrowableFunction<T, R, F extends
Exception>) --> Stream<R, E | F>
Or perhaps from the beginning of Java, adding a checked exception to a "throws"
colors the function the same way the keyword async or Rust lifetime type
information color a function [1]. Adding a checked exception to a function is
not a backward compatible change.
So we have some of idioms that beginners need to internalize to try workaround
checked exceptions,
- The oldest is i think, Thread.currentThread().interrupt() which allows to
silence an InterruptedException at the price of continuing to execute the code
until the next blocking call
- UncheckedIOException and IOError that both wrap an IOException into an
unchecked exception allowing tunneling of checked exceptions ; wrapping an
IOException and unbundle it later.
- Unsafe.rethrow (this one was retired)
- the unfamous rethrow using erasure to see a checked exception as an unchecked
exception
static <T extends Throwable> AssertionError rethrow(Throwable cause) throws T {
throw (T) cause;
}
- IntelliJ has changed the default try/catch snippet to instead of calling
printStackTrace() to throw a RuntimeException wrapping the exception.
This simple change is i believe the best change to Java in the recent years
(perhaps toes to toes with records), at least now the code of my students does
not print the stack trace and resume its course when an exception occurs.
try {
...
} catch(FooException e) {
throw new RuntimeException(e);
}
Also no language presented as potential successor of Java, neither Scala nor
Kotlin have checked exceptions, because functions with checked exceptions do
not compose.
If Java wants to be the next Java, we will have to drop checked exceptions at
some point.
The good news is that seeing all exceptions as unchecked exceptions is a
backward compatible change, "throws" can still be supported for documentation
purpose, the compiler can emit a warning instead of an error if there is no
catch corresponding to a checked exception and allow everyone to catch any
exceptions in the code.
I think we should recognize that the idea of checked exceptions was a good idea
on paper but not a good one in practice and work to retire the concept of
checked exceptions from Java.
Rémi
[1] https://journal.stuffwithstuff.com/2015/02/01/what-color-is-your-function/
> From: "Nathan Reynolds" <[email protected]>
> To: "amber-dev" <[email protected]>
> Sent: Saturday, November 12, 2022 5:47:10 AM
> Subject: Throwing Functions
> I am sorry if I am very late to the discussion. Consumer, Function, Predicate,
> and Supplier don't allow for throwing checked exceptions. This feature is
> needed in many cases. What about adding a variant that allows for throwing
> checked exceptions? For example, a ThrowableConsumer could be defined as
> such...
> public interface ThrowableConsumer<T, E extends Throwable>
> {
> void accept(T t) throws E;
> }
> A method that receives this as a parameter could be defined as such...
> public <E extends Throwable> void process(ThrowableConsumer<T, E> consume)
> throws E
> {
> ...
> }
> The compiler takes care of ensuring the checked exception is dealt with in the
> caller to process().