Thursday, December 19, 2013

Lift error handling with lens-like syntax

One of the main deficiencies of transformers is that the MonadTrans class does not let you lift functions like catchError. The mtl library provides one solution to this problem, which is to type-class catchError and throwError using the MonadError type class.

That's not to say that transformers has no solution for lifting catchError and throwError; it's just really verbose. Each module provides a liftCatch function that you use to lift a catchError function from the base monad to the transformed monad.

For example, Control.Monad.Trans.State provides the following liftCatch function:

The k in the above function will be a series of composed liftCatch functions that we will apply to catchError. However, in the spirit of the lens library we will rename these liftCatch functions to be less verbose and more hip and sexy:

This approach has a few advantages over the traditional MonadError approach:

You get improved type inference

You get type errors earlier in development. With MonadError the compiler will not detect an error until you try to run your monad transformer stack.

You get better type errors. MonadError errors will arise at a distance where you call runErrorT even though the logical error is probably at the site of the catchError function.

Functional references are first class and type classes are not

However, we don't want to lift just catchError. There are many other functions that transformers can lift such as local, listen, and callCC. An interesting question would be whether this is some elegant abstraction that packages all these lifting operations into a simple type in the same way that lenses package getters, setters, traversals, maps, and zooms into a single abstraction. If there were, then we could reuse the same references for catching, listening, and other operations that are otherwise difficult to lift: