3 ms·
(Just adding a bit to that:) It's not just subclassing. It also breaks with higher-order functions, e.g. what is the throws clause of a map() method on lists?
by Quekid5 7y ago
(Just adding a bit to that:)
It's not just subclassing.
It also breaks with higher-order functions, e.g. what is the throws clause of a map() method on lists? There's really no good answer in Java, so what people do in practice is to just re-wrap into a RuntimeException, but that leaves callers with a problem: Calling code cannot match on exception types using a catch clause on the checked exception -- it has to check for a RuntimeException with an embedded checked exception. This is disastrous for ergonomics and real-life use of checked exceptions.
The Result/Either method leads to its own ergonomic problems, but I think they could be solvable with support for row-types and (anonymous) union types. At least in Haskell, the Either method leads to a proliferation of FooError types whose only purpose is to wrap other error types. Well, either that or you accept that every little thing can return in a generic 'AppError'.
- barrkel 7y agoThe "right" thing in Java would be to parameterize map() by the error type declared on the lambda passed in. In other words: interface Function<T,U,Err> { U apply(T value) throws Err; } interface List<T> { <U,Err> List<U> map(Function<T,U,Err> f) throws Err; }
- lmm 7y agoThat only works for exactly one exception type. Err cannot be no errors, nor can it be two or more unrelated exceptions. You end up having to have n different overloads (for 0, 1, 2, ... exceptions) for every functions, and since Java's type system is not up to "varidic generics" you have to write them all manually.
- Nullabillity 7y agoYou could solve that with sum types (for 2+ exceptions) and never types (for 0),
- lmm 7y agoIt's not actually a sum, it's an inclusive union which are much harder to reason about and compromise your type system more (IMO). In any case, Java does not have any of those, and this is what makes checked exceptions impractical to work with; Rust has sum types but I don't think it has either inclusive unions or never types.
- Nullabillity 7y ago> It's not actually a sum, it's an inclusive union Right, whoops. My bad! > In any case, Java does not have any of those, and this is what makes checked exceptions impractical to work with Absolutely. I was approaching it from the standpoint of "what would I change about Java's type system to make checked exceptions usable?". > Rust has sum types but I don't think it has either inclusive unions or never types. Rust is working on stabilizing a true never type[0], and until then you can emulate it with an empty enum.[1] There is also a RFC for unions, but that doesn't seem to have gone anywere.[2] [0]: https://github.com/rust-lang/rust/issues/57012 https://github.com/rust-lang/rust/issues/57012 [1]: Such as https://crates.io/crates/void https://crates.io/crates/void which also provides helpers for safely coercing `Void` `Result`s [2]: https://github.com/rust-lang/rfcs/pull/1154 https://github.com/rust-lang/rfcs/pull/1154
- Quekid5 7y agoMy comment was actually made with many of these issues in mind -- I was trying to avoid needless nitpicking by being a bit vague. Perhaps that was a mistake. The point wasn't that these things couldn't be solved -- they could, at least in theory[0] -- but that the current practical limitations of Java[1] are such that the interaction of higher-order functions and checked exceptions make checked exceptions a really bad idea. [0] At least I think so... given anonymous or adhoc sum types, etc. [1] Interestingly, this is not a JVM-wide issue. It's just the Java compiler that imposes these restrictions, for example: You cannot "catch" a checked exception which hasn't been declared at least one method in the "try" scope. This makes sense at first glance ("cannot happen"), but means that re-wrapping by higher-order functions must entail extremely error-prone inspection of any exception by calling code, etc. etc. It's a huge mess.
- barrkel 7y agoOh indeed, you need |, and the ability to compose the type parameters, etc. It's why I said "right" rather than right.
- cjhopman 7y agoWhile you're correct about the two or more unrelated exceptions, Java will happily infer Err to RuntimeException and so the no errors case works just fine there. It's only work in a case where the type is only needed by inference, though.
- Quekid5 7y agoA bit late... but as others have pointed out: This only works to the first order. If you have functions-that-call-functions-that-call-functions, you end up not being able to write(!) the correct throws clauses in Java.