Language feature to improve checked exceptions

Tagir Valeev amaembo at gmail.com
Sun Mar 5 08:54:21 UTC 2023


Hello!

You can make something like this with a library code, using existing Java
language functionality (you don't even need unchecked casts!).
Here's a rough sketch:

record Rethrower<SRC extends Throwable, DST extends Throwable>(Class<SRC>
cls, Function<SRC, DST> mapper) {
public void run(ThrowableRunnable<SRC> r) throws DST {
get(() -> {
r.run();
return null;
});
}

public <R> R get(ThrowableComputable<R, SRC> r) throws DST {
try {
return r.get();
}
catch (Throwable t) {
if (cls.isInstance(t)) {
DST dst = mapper.apply(cls.cast(t));
dst.setStackTrace(t.getStackTrace());
throw dst;
}
if (t instanceof RuntimeException re) {
throw re;
}
if (t instanceof Error e) {
throw e;
}
throw new IllegalArgumentException("Supplied function throws undeclared
checked exception", t);
}
}
}
@FunctionalInterface
interface ThrowableRunnable<E extends Throwable> {
void run() throws E;
}
@FunctionalInterface
interface ThrowableComputable<R, E extends Throwable> {
R get() throws E;
}

Could be used like this:

List<String> list =
files.map(p -> new Rethrower<>(IOException.class,
UncheckedIOException::new).get(()
-> Files.readString(p)))
.toList();

Note that this allows to extract and reuse a particular rethrow strategy:

static final Rethrower<IOException, UncheckedIOException>
IO_RETHROWER = new Rethrower<>(IOException.class, UncheckedIOException::new
);
...
List<String> list = files.map(p -> IO_RETHROWER.get(() -> Files.readString(p
))).toList();

With best regards,
Tagir Valeev.

On Sun, Mar 5, 2023 at 12:49 AM Tom L <tom_l64 at hotmail.com> wrote:

> Hello, it's the first time I send an email here, I have no idea how things
> work, so I hope I am doing things somewhat correctly.
>
>
>
> I wanted to make a suggestion about exceptions :
>
> Checked exceptions are used as a meant of a second return type, which
> shouldn't happen in most cases, and then, depending of what the caller
> wants to do, it will handle it in a certain way. So in some sort, a checked
> exception is like part of the return type, like you would have a
> Result<Success, Failure>, compared to unchecked exception in java which are
> just supposed to be bugs.
>
> But this feature causes some burden, so much so that some languages, even
> languages compiling to java (ie kotlin) got rid of checked exceptions, and
> other languages like Rust use a Result<Success, Failure> which, which, with
> enough language constructs, can be quite good.
>
> While I agree that not having checked exceptions nor Result but instead
> only unchecked ones would be a bad idea, because it would mean that a part
> of the return type is unknown, which we wouldn't want in a strongly typed
> language, I believe some action needs to be taken.
>
>
>
> In my opinion, one of its burdens is caused by the try-catch language
> feature :
>
> String text;
>
> try {
>
>                 text = Files.readString(somePath);
>
> } catch(IOException ex) {
>
>                 throw new UncheckedIOException(ex);
>
> }
>
> //do something with text
>
> This is a common example, of how you would use it : you want to read a
> string, and an IO error shouldn't happen, so you fail-fast
>
> The reason why I didn't use text inside the catch, is because the catch
> should only be for this specific exception, and also it would add another
> level of nesting, which would start to hurt when other ifs or try-catches
> appear.
>
> This code is boilerplate and is error-prone, since you always have to
> repeat the same lines, it is also weird for any non java programmer.
>
> And this is far from being the worst, because another common example is
> with streams, since you can't throw checked exception, you have to handle
> each time, in each stream operation, and even if you wrap this code in
> methods and use method references, it's still far less readable than having
> a short lambda where you see exactly what the stream is doing.
>
> If checked exceptions were instead a Result type, a solution to this
> problem would be to make a unwrap() method (like in Rust, or like with
> Java's Optional#orElseThrow())
>
> String text = Files.readString(somePath).unwrap();
>
>
>
> So my suggestion is that, since catching is a language feature, it can
> only be dealt with another language feature :
>
> String text = Files.readString(somePath) throw IOException ex as new
> UncheckedIOException(ex);
>
> If this method throws an IOException, it will rethrow it as an
> UncheckedIOException.
>
> About the syntax, I used "throw" since it's an already used keyword, but
> depending of the meaning, it could be "catch" or whatever, and "as" could
> be "->" if using a contextual keyword is too much.
>
> This code could even be simplied as the following with new methods or
> language features, in the future :
>
> String text = Files.readString(somePath) throw IOException ex as
> ex.unchecked();
>
> String text = Files.readString(somePath) throw IOException as unchecked;
>
> etc.
>
> Unchecked part could either be the same exception except the compiler
> ignores it (I know this is possible since it's possible to throw a checked
> exception as an unchecked), the idea is that since you are telling the
> compiler that if an exception happens, then it should fail-fast, then the
> compiler should be able to say "ok, I trust you".
>
> An alternative would be that the unchecked simply wrap it in a
> RuntimeException or a specific unchecked exception.
>
>
>
> So, what's the point of this, does it only serve this use case ?
>
> So first, is it important to note that it isn't just for checked ->
> fail-fast, but also checked -> some other checked, when exception
> conversion is needed, which can be useful, and also unchecked ->
> checked/unchecked, for example if an API only provides an unchecked like
> Integer#parseInt.
>
> Now about the use case : this syntax, isn't just a compressed try-catch,
> it's also an expression, which is very important, because not only you can
> very clearly handle this kind of cases, but it can provide easy to read,
> concise code in lambdas  and assignments, for example :
>
> try (var files = Files.list(path) {
>
>                 return files.filter(this::matcher)
>
>                                                .map(p ->
> Files.readString(p) throw IOException as unchecked)//Possible syntax
> Files::readString throw IOException as unchecked ?
>
>                                                .toList();
>
> }
>
> And in the future, a new method could be added for streams called
> .continueOnFailure(IOExcepion.class) (or UncheckedIOException if the
> unchecked wraps the exception instead of marking it as unchecked) for
> example, which would continue even if there is an exception.
>
> An additional syntax could also be provided for cases where catching is
> used as an if else :
>
> OptionalInt parseInt(String s) {
>
>                 return OptionalInt.of(Integer.parseInt(s)) catch
> NumberFormatException -> OptionalInt.empty();            // Using -> syntax
> here instead of "as" to show that it can also make sense
>
> }
>
>
>
> I hope it can fix this unholy war of checked exceptions which never seem
> to advance.
>
>
>
> Sincerely.
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/amber-dev/attachments/20230305/98c4b05d/attachment-0001.htm>


More information about the amber-dev mailing list