Notice that part of the semantics of this function—that it may throw an HttpException—is encoded in a comment, which the compiler cannot check. This is because Haskell’s notion of exceptions offers no mechanism for advertising to the user the fact that a function might throw an exception.

Michael Snoyman discusses some solutions to this problem, as well as some common anti-patterns, in his blog post Exceptions Best Practices. However, wouldn’t it be much nicer if we could simply express in the type that simpleHttp may throw a HttpException? In this blog post I will propose a very lightweight scheme to do precisely that.

If you want to experiment with this yourself, you can download CheckedRevisited.hs (tested with ghc 7.2, 7.4, 7.6, 7.8 and 7.10).

Throwing checked exceptions

Let’s introduce a type class for “checked exceptions” (à la Java):

classThrows e where

Here’s the key idea: this will be a type class without any instances. If we want to record in the type that some IO action throws a checked exception, we can now just add the appropriate type class constraint. For instance, we can define

Type annotations

There’s something a little peculiar about a type class constraint such as Throws HttpException: normally ghc will refuse to add a type class constraint for a known (constant) type. If you were to write

foo = throwChecked $ userError "Uhoh"

ghc will complain bitterly that

No instance for (Throws IOError)
arising from a use of ‘throwChecked’
In the expression: throwChecked

until you give the type annotation explicitly (you will need to enable the FlexibleContexts language extension):

foo ::Throws IOError =>IO a
foo = throwChecked $ userError "Uhoh"

I consider this a feature, not a bug of this approach: you are forced to explicitly declare the checked exceptions you throw.

Catching checked exceptions

In order to catch checked exceptions we need to somehow eliminate that Throws constraint. That is, we want a function of type

catchChecked ::Exception e => (Throws e =>IO a) -> (e ->IO a) ->IO a

In the remainder of this section we explain how we can do this; it requires a bit of type level hacking, and the use of roles. Bear in mind though that you do not need to understand this section in order to be able to use checked exceptions; it suffices to know that a function catchChecked exists.

First, we define a newtype wrapper around an action that throws an exception:

newtypeWrap e a =Wrap { unWrap ::Throws e => a }

Then we define a newtype wrapper around the exception themselves:

newtypeCatch e =Catch e

This Catch is used internally only and not exported; it is the only type that will get a Throws instance:

instanceThrows (Catch e) where

Now we’re almost there. We are going to use coerce to pretend that instead of an exception of type e we have an exception of type Catch e:

coerceWrap ::Wrap e a ->Wrap (Catch e) a
coerceWrap = coerce

This requires the type argument e on the Throws class to be representational (this needs IncoherentInstances):

which has the Throws annotation on returnAction itself; this means we can make the annotation disappear by adding an exception handler to returnAction even though it’s never called (because returnActionitself never throws any exception).

This is somewhat unfortunate, but it occurs only infrequently and it’s not a huge problem in practice. If you do need to return actions that may throw exceptions, you can use a newtype wrapper such as Wrap that we used internally in rethrowUnchecked (for much the same reason):

Of course you will probably want to define a datatype that is more meaningful for your specific application; for instance, see see the definition of HttpClient in the Repository.Remote module, which defines something like

Conclusions

does not tell you that this function can only throw HttpExceptions; it can still throw all kinds of unchecked exceptions, not least of which asynchronous exceptions. But that’s okay: it can still be incredibly useful to track some exceptions through your code.