5 ms·
This is very simplistic view of the problem. This completely glosses over modularity, ABI, performance optimizations... just to name a few. How are you going
by crabbone 3y ago
This is very simplistic view of the problem. This completely glosses over modularity, ABI, performance optimizations... just to name a few.
How are you going to write generic functions that take functions as arguments and re-throw the errors thrown by these functions, if you use checked exceptions? Will you require that the acceptable functions only throw exceptions that you like? -- Then your generic function is close to being worthless...
If exceptions are encoded in function's interface, then they have to be in ABI, but then you must have non-trivial types available when marshalling data between two components, so, you cannot serialize the communication using some protocol with a fixed number of types (eg. JSON and friends), because now you need to account for the infinite variety of exception types.
Because of at least these two things, what I saw happen a lot of Java / C++ projects (god blessed me with very little C# exposure) was that as soon as a developer encountered a function with checked exceptions, a wrapper was written which changed the type into a runtime exception. This is so because exceptions are supposed to be handled separately, and often the author of the function has no idea how they need to be handled -- so they want to concentrate on the main goal of the function. Once functions grow into garlands of try-catch-catch-catch-...catch the focus is lost. It becomes very hard to understand why the function was written in the first place, because the error handling takes over every other concern.
- interlocutor 3y ago> write generic functions that take functions as arguments and re-throw the errors thrown by these functions There is a philosophy that applies here: simple things should be simple, complex things should be possible. The scenario you're mentioning is not common enough that the language design should be centered around it.
- newaccount74 3y agoIt's a very common pattern, almost every modern language has a "map" function. The map function can throw a superset of the exceptions that its argument can throw. If checked exceptions can't deal with this, they'll be of limited use.
- interlocutor 3y agoIn the case of map function hopefully you're using it with methods that don't fail in serious ways, and don't need strong error recovery. If so Java has RuntimeException to handle that case. If serious errors are possible and strong error recovery is needed, then you need to avoid the conveniences offered by functional style programming.
- Quekid5 3y agoThe problem is that if some code you call throws a checked exception (InterruptedException being an extremely common culprit) then you must wrap... and suddently nobody calling YOUR code can catch that InterruptedException reliably because it's now a SomeException (doesn't even have to be RuntimeException specifically) with an added "suppressed" exception that you now have to check for. ... so the basic "catch" syntax starts to fall apart because now you have to catch everything and resort to stuff like Guava's Throwables helpers. It's madness. The problem ultimately is variance: Methods are covariant, but throws clauses must be contravariant. There are ways to solve this but "checked exceptions" (as in Java) are not the right way. Ask anyone who's worked in Scala on the JVM which they prefer and you'll have your answer.
- dasyatidprime 3y agoIt's possible to use a type parameter in a throws clause in Java, last I checked (as I also mentioned in more detail above). But it doesn't seem to be common practice, so interop is still a disaster.
- Quekid5 3y agoThat only works up to one checked exception type, because Java doesn't have anonymous union types. It really is a lose-lose unless Java grows a more powerful system around this.
- crabbone 3y agoSorry. This is nonsense. map() is the bread and butter of any program. Besides, you cannot ever decide whether an exception is important or not -- it's always in the purview of the user. Re-throwing a non-checked exception is what I described as the usual / typical coping mechanism in languages with checked exceptions. Which is obviously a way to negate the whole feature.
- dasyatidprime 3y ago> How are you going to write generic functions that take functions as arguments and re-throw the errors thrown by these functions, if you use checked exceptions? I've actually done this with an interface type parameter used in a throws clause in Java before, but I'm not sure how it interacts with module boundaries, it's kind of verbose, and it certainly didn't seem to be common practice. But it did work: the HOF-ish method that took a parameterized ThingFrobber<E> and used it to frob things unchecked would compile only if it also threw E, and the method passing the HOF a ThingFrobber (I think via lambda syntax) could catch the associated concrete checked exception type to sink it. IIRC I was using it to allow a checked early exit from a complex iteration, and it even propagated a slightly complex bound with multiple checked exception types more smoothly than I'd expected it to.
- iainmerrick 3y agoRight, I was thinking that this is the answer to the supposedly intractable problem posed by the original article. Just make your ExampleInterface generic -- ExampleInterface<T>.foo() throws T. That technique works really well, and I don’t understand why it isn’t more widely used (or at least it wasn’t when I last used Java for serious work).
- duped 3y ago> Will you require that the acceptable functions only throw exceptions that you like? -- Then your generic function is close to being worthless... Constrained generic parameters are actually super useful. > If exceptions are encoded in function's interface, then they have to be in ABI They already are in Itanium > you cannot serialize the communication using some protocol with a fixed number of types (eg. JSON and friends), because now you need to account for the infinite variety of exception types. No, the only exception that arises is ser/deserialization error. You can also trivially represent an error in JSON using an object, which is exactly what JSON RPC protocols do. It's also never safe to throw across an FFI boundary and similarly nonsensical to throw across a serialization boundary, so I'm not sure why you'd care.
- crabbone 3y ago> Constrained generic parameters are actually super useful. Then you missed the main point: if you require the generic function to accomodate all sorts of kinds of exceptions raised by the functions it may call, this requirements now propagates to every function it may call. So, you end up implementing functions with unnecessary "throws" because they might be used in a generic function which had to add that clause because of some other completely unrelated function. Alternatively, you aren't writing generic code at all, you just write two implementations which happen to have similar names.