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?
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
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 Monad
s, it doesn't really present a problem as the compiler still handles it just fine.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With