4 ms·
The biggest problem with that is that it is very unwieldly if you're using any kind of higher-order functions. To fix that, you need to start supporting error
by simiones 6y ago
The biggest problem with that is that it is very unwieldly if you're using any kind of higher-order functions.
To fix that, you need to start supporting error polymorphism. For example, `map` should have a signature like
map :: List a -> (a -> b -[err]) -> List b -[err]
So that
map [1 2 3] +1 //no errors
map [1 2 3] sendOnNetwork //returns NetworkError
At least, this is one of the major limitation of Java's Checked Exceptions idea, one which often forces you to use Unchecked Exceptions.
Another common problem is handling exceptions which occur while handling another exception. D has the best default I've seen in that area (it will automatically collect all of the errors, instead of panic()-ing like C++ or discarding the old exception like Java and C#).
- junke 6y agoAnd if the function can throw different kinds of exceptions, you need a way to express the union of unrelated exceptions (without defining a new variant type), a bit like Polymorphic Variants I guess.
- dllthomas 6y agoIdeally also a way of saying "... except for X Y Z" while remaining appropriately polymorphic. I've remarked before that while checked exceptions help make sure you consider every possible situation, imprecision forces you to consider many (too many?) impossible situations as well.
- dasyatidprime 6y agoThere've been a few times I've used checked exceptions very locally in Java where I did use a type parameter successfully for this—though it's not really threaded through the type signatures in the standard libraries, so interop can be a problem. Almost like what you wrote, of course more verbosely, something like: interface SomeProcessor<E> { void process(Thing thing) throws E; } <E> void processThingsSomehow( SomeProcessor<E> processor, Container<Thing> things, Parameter how) throws E { // ... { processor.process(extractedThing); // } ... } and then later: void processOrFail(Thing thing) throws SomeCheckedException { // ... } void processOurThings() { try { someObject.processThingsSomehow( this::processOrFail, getThings(), HOW); } catch (SomeCheckedException e) { // ... } } and it definitely worked the way I expected—if the correct exceptions weren't caught in processOurThings, it wouldn't compile, and processThingsSomehow did not have to catch them. It even worked in at least some cases with multiple throws on the concrete SomeProcessor, though I think the different exceptions involved had an upper type bound within the package; I don't know how well that's handled in the general case.
- simiones 6y agoNice, I never actually tried to do this (my problem was more that I needed to use built-in functions that don't throw, for example sorting a list with a Comparator which can throw).
- a1369209993 6y ago> To fix that, you need to start supporting error polymorphism. > map :: List a -> (a -> b -[err]) -> List b -[err] FWIW, Haskell has this already; it's called[0] Traversable: ghci> :t mapA mapA :: (Traversable t,Applicative f) => (a -> f b) -> t a -> f (t b) ghci> :t mapA @(Error ||) @[] mapA :: (a -> Error || b) -> [a] -> Error || [b] 0: The default prelude calls it `traverse` instead of `mapA` (which is terrible for reasons that should be obvious[1]) and `Either Error a` instead of `Error || a` (which is a matter of taste). 1: Especially if you rename the method of Functor from `fmap` to: map :: Functor t => (a -> b) -> t a -> t b -- for comparison: mapA :: ... => (a -> f b) -> t a -> f (t b)