4 ms·
Java's implementation of checked exceptions looks pretty minimal and sound to me. How would you implement them?
by hota_mazi 7y ago
Java's implementation of checked exceptions looks pretty minimal and sound to me.
How would you implement them?
- Fellshard 7y agoIt's certainly fair to say I have no alternative recommendation. I enjoyed using them prior to Java 8, and they gave the guarantees I was looking for. They have just had a much harder time integrating with newer features than could be hoped for. I'm not sure how much of that is intrinsic to checked exceptions, and how much is intrinsic to Java's implementation. One example of this: there is no way to encode the type of a checked exception in a generic. I would like to be able to express something like the following: interface ExceptionHandler<E extends Exception, S, T> { T wrap( Function<S, T, throwing E> fn, Function<E, T> exceptionMapper ); } But there is no way to express that 'throwing E' part. The type of a checked exception is firmly embedded in the interface. So I can't supply, say, an IO method as a Function<S, T> parameter, since the IOException causes a mismatch, and I'd have to write a handler specifically for methods that throw IOException, another for those that throw TimeoutException, a third for those that throw IOException AND TimeoutException, etc; a fairly fruitless goal without automatic code generation.
- lmm 7y agoYou can actually use a generic parameter in a throws clause, so you can write something like: interface FunctionThrows1<S, T, E1 extends Throwable> { T apply(S in) throws E1 } interface FunctionThrows2<S, T, E1 extends Throwable, E2 extends Throwable> { T apply(S in) throws E1, E2 } ... But you still have to write one version of your method for each arity of throwing that you want to be able to wrap.
- Fellshard 7y agoInteresting. I tried this years ago, and it never worked. Maybe that's changed in recent versions? Arity versions are still more feasible at least, and many languages are willing to pay the cost for them.
- xenomachina 7y agoYeah, I'm pretty sure there were some compiler bugs involving generic exceptions around 1.5 - 1.6. (I don't know if they ever got fixed -- I switched to Kotlin.)
- twic 7y agoAFAICR this has always worked. But you rapidly run into limitations - as i say in another comment, catch and throw are not themselves generic. Plus, none of the interfaces used in the streams API have exception parameters.
- pron 7y ago> Plus, none of the interfaces used in the streams API have exception parameters. That's partly because there's another problem. Potentially, you could have streams of the form, say, `...map(A::foo).map(A::bar).map(A::baz).collect(...)`, and each of foo, bar, baz adds another exception type to the set that can be thrown by collect.
- twic 7y agoResult/Either-based streams have exactly the same problem. An exception-supporting stream API would solve it the same way: a function can either throw no exception, or an exception of the same type as thrown by a previous operation. You could have a .mapException operation which could change the exception type, like Result::map_err in Rust. In practice, you would end up with the stream throwing an exception of some general type (in 95% of cases, IOException!), but you could still write specific catch blocks for each possible subtype.
- pron 7y agoLet me put it this way: the message I got from the language team is that the problem is certainly acknowledged, and various alternatives to dealing with it are known, but none of them has so far been shown the clearly preferable, "good" choice, so until one presents itself, the decision is to do nothing rather than add/remove features that could later have other negative consequences.
- xenomachina 7y agoThe 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.)
- username90 7y agoChecked exceptions are just sum types and you unpack it with a try catch block.
- xenomachina 7y agoYes, 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.
- twic 7y agoYou 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.