Question

Why isn't MonadTrans defined as

class MonadTrans t where
    lift :: (Monad m, Monad (t m)) => m a -> t m a
--                    ^^^^^^^^^^^

instead of current

class MonadTrans t where
    lift :: Monad m => m a -> t m a

This is Haskell 98 (unlike the suggestion in Why aren't monad transformers constrained to yield monads?) and ensures that the result will always be a monad. Is there a reason why monad transformers are allowed to produce something that isn't a monad?

Was it helpful?

Solution 2

My guess is that a MonadTrans transforms a Monad into something else, instead of transforming a Monad into a Monad. It's more generalized, since you might write something that transforms a Monad and you can define lift, but you can't define >>= and return. Since most (if not all) MonadTrans instances end up being Monads, it doesn't really present a problem as the compiler still handles it just fine.

OTHER TIPS

bheklilr's answer gave me idea of an example where a monad transformer produces something that isn't a monad. A well-known example of something that isn't a monad is ZipList. And we can make a variant that runs a monadic action at each level:

import Control.Applicative
import Control.Arrow ((***))
import Control.Monad
import Control.Monad.Trans

-- | A list where each step is produced by a monadic action.
data ListT m a = Nil | Cons (m (a, ListT m a))

This is actually a monad stream. And it can be easily made into a Functor and an Applicative

instance Monad m => Functor (ListT m) where
    fmap f Nil      = Nil
    fmap f (Cons k) = Cons $ (f *** fmap f) `liftM` k
instance Monad m => Applicative (ListT m) where
    pure x = Cons $ return (x, pure x)
    Cons mf <*> Cons mx = Cons $ do
        (f, fs) <- mf
        (x, xs) <- mx
        return (f x, fs <*> xs)
    _       <*> _       = Nil

but obviously not a monad. So we have a MonadTrans instance that converts a monad into something that's only an Applicative.

instance MonadTrans ListT where
    lift mx = Cons $ (\x -> (x, lift mx)) `liftM` mx

(This whole thing made me realize that an experimental ZipSink in conduit-extra is also a nice such example.)


However, this raises another question: If we want such transformers, what laws should they adhere to? The laws for MonadTrans are defined as

lift . return = return
lift (m >>= f) = lift m >>= (lift . f)

So in our case we could wish for something like

lift (f `liftM` x)  = fmap f (lift x)

lift . return       = pure
lift (m `ap` f)     = lift m <*> lift f

I'm going to disagree with the other two answers to say that the result should be a monad. The reason why is that otherwise there are no sensible laws that lift should obey.

lift is supposed to be a monad morphisms, meaning that it should obey the following two laws:

lift (return x) = return x

lift (m >>= f) = lift m >>= \r -> lift (f r)

These laws make more sense when you realize they are functor laws between two Kleisli categories:

-- i.e. "fmap id = id"
(lift .) return = return

-- i.e. "fmap (f . g) = fmap f . fmap g"
(lift .) (f >=> g) = (lift .) f >=> (lift .) g

However, if you don't constrain the output to be a monad then those laws are no longer valid and you have no sensible way to verify that you implemented lift correctly.

I suspect the real reason was to make the class Haskell98

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top