Hacker News new | ask | show | jobs
by xenomachina 2377 days ago
The problem boils down to the fact that you can have a disjunction of any number of checked exception types (including zero). No other party of the type system allows disjunctions, so it causes a lot of problems. The checked exception is conceptually part of the return type, but is split out.

I wish Kotlin had, instead of ignoring the existence of checked exceptions, instead translated them into part of the return type. I use Kotlin a lot these days, and one annoyance for me is dealing with code that throws exceptions. They fixed the annoying "(almost) anything can be null" problem of java and replaced it with an equivalent problem. Why can't nullability and failure results both be part of the static type?

(The workaround it to manually use an Either type yourself, but it doesn't help you with calling anyone else's code, since virtually everything throws exceptions on failure.)

2 comments

Checked exceptions are just sum types and you unpack it with a try catch block.
Yes, but they are the only part of Java's type system that allows sum types.

How do you declare a method that generically takes a function that in turn takes a K, returns a V, and can throw whatever checked exceptions it wants, and you'll rethrow them? Last I checked, this wasn't possible in Java.

If checked exceptions were instead replaced with sum type return values, then it becomes trivial.

You can do that and propagate the exception:

    @FunctionalInterface
    interface ThrowingFunction<T, R, E extends Exception> {
        R apply(T argument) throws E;
    }

    static <T, R, E extends Exception> R apply(T argument, ThrowingFunction<T, R, E> function) throws E {
        return function.apply(argument);
    }

But not catch and rethrow it, because throw and catch aren't generic, and the type parameter isn't reified. You can emulate reified generics with the usual trick of passing a Class object:

    static <T, R, E extends Exception> R apply(T argument, ThrowingFunction<T, R, E> function, Class<E> witness) throws E {
        try {
            return function.apply(argument);
        } catch (Exception e) {
            throw witness.cast(e);
        }
    }
But this is pretty horrid.
Right, it's horrid, and it only works for exactly one checked exception type. So there's no way to define a single `Stream.map` (for example) that takes a function that throws whatever number of checked exceptions and just propagates them.
Either/Result also only works for exactly one error type.
Well, yes. In a way that's another example of the kind of problem I'm talking about.

This thread stemmed from https://news.ycombinator.com/item?id=21808522 :

> Java's implementation of checked exceptions looks pretty minimal and sound to me. > > How would you implement them?

Java's checked exceptions aren't "minimal" and are problematic because they behave unlike the rest of the types system. I'm not saying that they should have ditched checked exceptions and left it at that, though. What they should have done was make sum types a first-class part of the type system, and then checked exceptions become redundant.

In languages that actually have general sum types rather than special-casing the way Java's exceptions do, you can get multiple types of errors with an Either by making a union of the different types of errors. You can alternatively use an N-way sum type as your return value in place of the 2-way Either.

Kotlin has sealed classes, which can be used to create a sum type, and you can even do the same in Java with enough boilerplate, but in either of those languages you run into another problem if you try this: it doesn't interoperate well with the vast majority of existing library code that throws exceptions.

Incorrect for two reasons:

1) They follow a different code path (which is what makes them a superior way to handle errors over return values).

2) They cannot be emulated by sum types when all you want to do is not handle the exception and let it bubble up the stack frames.

The problem is that try catch is so verbose
You might like Rust :)