Parameterised monads

This post assumes some knowledge of Haskell, in particular what monads and monad transformers are. It's literate Haskell, which means you can load it into ghci directly (well, you'll probably have to convert HTML entities to plain text first, particularly > and <.)

> {-# LANGUAGE NoImplicitPrelude, MultiParamTypeClasses, FunctionalDependencies #-}
> {-# LANGUAGE FlexibleInstances, FlexibleContexts, UndecidableInstances #-}
> 
> import Prelude hiding (Monad (..))
> import qualified Prelude as P
> import Control.Monad.Trans

On the Haskell blog circuit recently the notion of "parameterised monad" has been floating about, provoked by a new paper "Parameterized Notions of Computation". I'll refrain from explaining it myself, instead linking to Dan Piponi's post on the topic. If you don't already know what parameterised monads are, go and read that.

It seems that a module has already been written to encode parameterised modules and uploaded to Hackage: Control.Monad.Parameterized (package monad-param). It dates from before the current excitement (2007 and it's at version 0.0.2) so the concept is apparently not new. It does however take a somewhat different approach, one that's seemingly more general but also more cumbersome. The change in approach is explained in a blog post by its author, Edward Kmett. Here's what the "parameterized monad" class would look like if translated from the paper:


> class PMonad m where
>     returnP :: a -> m p p a
>     (>>>=)  :: m p1 p2 a -> (a -> m p2 p3 b) -> m p1 p3 b

As Piponi explains, if the Prelude monad is some kind of generalised monoid where return is the unit and (>>=) is the operation, this class is a kind of generalised category where returnP is the identity and (>>>=) is arrow composition. Notice that you can only compose two arrows if their 'input' and 'output' types match. Now clearly we can set p1 = p2 and then ordinary monads fall out as a special case:


> -- newtype wrapper, with two phantom types to get the correct kind
> -- (m :: * -> *, UnparameterizedMonad m :: * -> * -> * -> *)
> newtype UnparameterizedMonad m p1 p2 a = UM { runUM :: m a }
>
> instance P.Monad m => PMonad (UnparameterizedMonad m) where
>     returnP       = UM . P.return
>     UM mx >>>= f  = UM $ mx P.>>= (runUM . f)

(P.Monad means the Prelude's concept of monad, whereas PMonad means parameterised monad.) What we have in Control.Monad.Parameterized is something like the following:


> class Return m where
>     returnM :: a -> m a
>
> class Fail m where
>     fail :: String -> m a
>
> class (Functor m, Functor m', Functor m'') => Bind m m' m'' | m m' -> m'' where
>     (>>=) :: m a -> (a -> m' b) -> m'' b
>     (>>)  :: m a -> m' b -> m'' b
>     x >> y = x >>= \_ -> y
>
> class (Fail m, Return m, Bind m m m) => Monad m
> instance (Fail m, Return m, Bind m m m) => Monad m

The last class basically says that Prelude monads are parameterised monads in which the parameter doesn't change:


instance P.Monad m => Return m where
    returnM = P.return

instance P.Monad m => Fail m where
    fail = P.fail

instance (Functor m, P.Monad m) => Bind m m m where
    (>>=) = (P.>>=)

(Unfortunately we can't guarantee a Functor instance for every Prelude monad - a known failing of the current Prelude. We also comment these instances out because they overlap with others.) Of course the thing to notice immediately is that this class doesn't make the parameters explicit; instead, the parameters can be provided any which way. The motivating example is a version of the State monad in which the type of the state can change.


> newtype State s1 s2 a = State { runState :: s1 -> (a, s2) }
>
> instance Functor (State s1 s2) where
>     fmap f (State trans) =
>         State $ \s -> case trans s of
>             (x, s') -> (f x, s')
>
> instance Return (State s s) where
>     returnM x = State $ \s -> (x, s)
>
> instance Fail (State s1 s2) where
>     fail = error
>
> instance Bind (State s1 s2) (State s2 s3) (State s1 s3) where
>     State trans >>= f =
>         State $ \s -> case trans s of
>             (x, s') -> case f x of
>                 State trans' -> trans' s'
>
> instance PMonad State where
>     (>>>=) =  (>>=)
>     returnP = returnM

What the more general setup provides is the ability to do things like this:


> instance Bind (State (s,t) (s',t')) (State s' s'') (State (s,t) (s'',t')) where
>     State trans >>= f = State $ \s -> case trans s of
>         (x, (s',t')) -> case runState (f x) s' of
>             (y, s'') -> (y, (s'', t'))

In this example, you can use a state transformer to change just one element of a paired state, without explicitly converting it. (Of course the usefulness of this is limited by only being able to do this on the left component - if you added one for the right as well, it would be ambiguous.) Another example:


> newtype StateT s1 s2 m a = StateT { runStateT :: s1 -> m (a,s2) }
> instance (Functor m, Return m, Bind m m m) => Functor (StateT s1 s2 m) where
>     fmap f (StateT trans) = StateT $ \s -> do
>         (x,s') <- trans s
>         returnM (f x, s')
>
> instance Return IO where
>     returnM = P.return
> instance Bind IO IO IO where
>     (>>=) = (P.>>=)
>
> instance Bind IO (State s1 s2) (StateT s1 s2 IO) where
>     mx >>= f =
>         StateT $ \s ->
>             mx P.>>= \x ->
>             case f x of
>                 State trans -> P.return (trans s)

So now you can have an expression like getContents >>= modify (map toUpper) and you automatically get a value in the transformer version of the second monad with the first monad as inner. Actually, even monad transformers become a special case in this framework:


> newtype Trivial a = Trivial { runTrivial :: a }
> instance Functor Trivial where fmap f (Trivial x) = Trivial (f x)
>
> instance (P.Monad m, Functor m, MonadTrans t, Functor (t m)) =>
>         Bind m Trivial (t m) where
>     mx >>= f = lift (fmap (runTrivial . f) mx)
>
> lift' :: (Functor m, Bind m Trivial m') => m a -> m' a
> lift' mx = mx >>= Trivial
>
> instance MonadTrans (StateT s s) where
>     lift mx = StateT $ \s -> mx P.>>= \x -> P.return (x,s)

lift' does the same as lift: notice that lift getContents and lift' getContents can both be instantiated to the type StateT s s IO String. (Semantically, they both perform the IO action and leave the state alone.) Monad transformers are just monads that can have some other monad bound to it on the left and are parameterised by that monad. Actually, lift' does more than lift: it generalises even generalised lifting functions like liftIO:


> instance (P.Monad m, Bind IO Trivial m) => MonadIO m where
>     liftIO = lift'

There is one big drawback to this implementation of parameterised monads, namely that the type of returnM can't be inferred reliably:


*Main> runState (returnM 1 >>= \x -> returnM x) ()

<interactive>:1:10:
    No instance for (Bind m m' (State () s2))
      arising from a use of `>>=' at <interactive>:1:10-38
    Possible fix:
      add an instance declaration for (Bind m m' (State () s2))
    In the first argument of `runState', namely
        `(returnM 1 >>= \ x -> returnM x)'
    In the expression: runState (returnM 1 >>= \ x -> returnM x) ()
    In the definition of `it':
        it = runState (returnM 1 >>= \ x -> returnM x) ()

Compare this to explicitly parameterised monads:


*Main> runState (returnP 1 >>>= \x -> returnP x) ()
(1,())

And normal monads:


Prelude Control.Monad.State> runState (return 1 >>= \x -> return x) ()
(1,())

The problem here is that State has two parameters and the type inferencer must fill them in, even though they're irrelevant, but it can't see what they must be because the second returnM could actually be in any monad.

This framework also has the following problem: the Bind class has two monads as arguments (plus the third that depends on the first two), so to get n monads to work together in all combinations that make sense, you'll need up to n2 instances. This looks like the start of a maintenance nightmare.

Leave a Reply