Saturday, March 16, 2013

mmorph-1.0.0: Monad morphisms

Several people have asked me to split off MFunctor from pipes so that they could use it in their own libraries without a pipes dependency, so today I'm releasing the mmorph library, which is the new official home of MFunctor. The upcoming pipes-3.2 release will depend on mmorph to provide MFunctor.

The mmorph library specifically targets people who make heavy use of monad transformers. Many common problems that plague users of monad transformers have very elegant solutions inspired by category theory and mmorph provides a standard home for these kinds of operations.

This post won't include examples because mmorph already features an extended tutorial at the bottom of its sole module: Control.Monad.Morph. The tutorial highlights several common use cases where the mmorph library comes in handy and I highly recommend you read it if you want to understand the concrete problems that the mmorph library solves.

Moving on up

mmorph takes several common Haskell idioms you know and love and lifts them to work on monads instead. The simplest example is a monad morphism:

MFunctor is the higher-order analog of the Functor class (thus the name), and the resemblance becomes even more striking if you change the type variable names:

class MFunctor f where
hoist :: (a :-> b) -> (f a :-> f b)

This parallel lets us reuse our intuition for Functors. An MFunctor wraps a monad in the same way that a Functor wraps a type, and MFunctors provide an fmap-like function, hoist, which modifies the wrapped monad.

If you've ever used monad transformers then you've probably already used MFunctors. Just check out the instance list for MFunctor and you'll see many familiar names:

... so you can think of a monad morphism as just a principled way to transform one monad into another monad for compatibility purposes.

Second, the hoist function from MFunctor defines a functor that transforms monad morphisms:

hoist (f . g) = hoist f . hoist g
hoist id = id

... so you can think of hoist as just a principled way to transform one monad morphism into another monad morphism for compatibility purposes.

The mmorph library is a concrete example of how functors naturally arise as compatibility layers whenever we encounter impedance mismatch between our tools. In this case, we have an impedance mismatch between our monad transformers and we use functors to bridge between them so they can seamlessly work together.

Have you read "Monads, Zippers and Views: Virtualizing the Monad Stack" by Schrijvers and Oliveira? It seems like your "hoist" is exactly their "tmap." I wonder if you think one could use mmorph to implement the same monad operations they describe in their paper?

I skimmed it once a long time ago back when I was learning monad transformers for the first time (it was way over my head back then). Having now read it again I see that `mmorph` basically corresponds to sections 4 and 5 of their paper. The idea is that the combination of `hoist` and `lift` acts like their structural mask (and they use `view`/`tmap`, but it's still the same basic idea).

For example, if you have a global transformer stack of type:

total :: t1 (t2 (t3 (t4 m))) r

... but you want to ignore layers t1 and t3. Then what you do is write a computation that assumes that only layers t2 and t4 are present:

sub :: t2 (t4 m) r

... then when you are done you can merge it into the global transformer stack using `hoist` and `lift`:

(lift . hoist lift) sub :: t1 (t2 (t3 (t4 m))) r

This lets you write `sub` in such a way that it ignores layers it does not need, and then the client can worry about getting it to unify with other monad layers through judicious use of `lift` and `hoist`. Those `lift`s and `hoist` are basically the "structural mask" they proposed.

However, there are several things in that paper that `mmorph` cannot do. For example, you cannot do bidirectional views, sophisticated liftings (of the kind described in section 6), or nominal liftings.

Thanks for bringing that paper to my attention. Now I see that there is prior art in the literature for `mmorph`. Neat! :)