Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

I think that exceptions are a problem and cause this developer burden only because they are invisible. If they appeared in the type signature, for example as

() -[DatabaseReadError]-> ()

then they would be part of a function's 'contract'.

With this, consumers of your function are making an active decision about whether to handle or bubble an exception without examining your implementation, and the type of the main function tells you which errors will end up being fatal.

Disclaimer: this is not a new idea. There are proposals for OCaml to add 'algebraic effects', which have similar annotations on arrows, and I believe there have been discussions around using the syntax to track exceptions.



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#).


There'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.


Nice, 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).


> 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)


And 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.


Ideally 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.


This is exactly Java's checked exceptions, which I'd call a failed experiment in language design.

The problem is that it makes the most common case (letting the exception bubble up) very inconvenient and clutter-y. Signatures with 5 different declared exceptions are worse than useless.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: