6 ms·
I love both, but Java's implementation of checked exceptions cause harder and harder problems, especially when combined with tools such as lambdas, since there'
by Fellshard 7y ago
I love both, but Java's implementation of checked exceptions cause harder and harder problems, especially when combined with tools such as lambdas, since there's no way to generically compose or handle checked exceptions inside them.
- hota_mazi 7y agoJava'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.
- 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.
- dwaite 7y agoProper Either/Result would fix some of the exception problems with lambdas in Java, though. It also allows the exceptional behavior to be defined and to be controlled by the developer, one thing that you don't really have today when throwing RuntimeExceptions with say Stream APIs.
- pron 7y agoJava's checked exceptions are "proper Either/Result." The problem is that they can be of a more complex types (union types) combined with subtyping. In other words, what you see with Either is not a result of it being written as a return type, but a result of it being a much simpler type than Java's exceptions.