Monad Transformers Tutorial
From HaskellWiki
Think about code in IO that needs to be able to break out of a loop:
forM_ [1..maxRetries] $ \i -> do response <- request i when (satisfied response) break
Reminder about "when":
when False _ = return () when True a = a
So, how would you implement "break"?
Another example:
do mc1 <- tryConnect "host1" case mc1 of Nothing -> return Nothing Just c1 -> do mc2 <- tryConnect "host2" case mc2 of Nothing -> return Nothing Just c2 -> do ..
(>>=)
(>>=)
IO (Maybe a)
Maybe
IO (Maybe a)
Maybe a
newtype MaybeIO a = MaybeIO { runMaybeIO :: IO (Maybe a) } instance Monad MaybeIO where return x = MaybeIO (return (Just x)) MaybeIO action >>= f = MaybeIO $ do result <- action case result of Nothing -> return Nothing Just x -> runMaybeIO (f x)
So now we can replace the above boilerplate code with:
result <- runMaybeIO $ do c1 <- MaybeIO $ tryConnect "host1" c2 <- MaybeIO $ tryConnect "host2" ..
Or if the tryConnect function wrapped its result in MaybeIO then we just have to use runMaybeIO there, and that's it. What happens if we now have some "print" in between?
result <- runMaybeIO $ do c1 <- MaybeIO $ tryConnect "host1" print "Hello" c2 <- MaybeIO $ tryConnect "host2" ..
MaybeIO a
IO ()
IO a
MaybeIO a
IO a
IO (Maybe a)
Just
transformIOtoMaybeIO :: IO a -> MaybeIO a transformIOtoMaybeIO action = MaybeIO $ do result <- action return (Just result)
And now we can do:
result <- runMaybeIO $ do c1 <- MaybeIO $ tryConnect "host1" transformIOtoMaybeIO $ print "Hello" c2 <- MaybeIO $ tryConnect "host2" ..
Now we can also break from the first example's loop!
break :: MaybeIO a break = MaybeIO $ return Nothing forM_ [1..maxRetries] $ \ i -> do response <- transformIOtoMaybeIO $ request i when (satisfied response) break
IO (Maybe a)
newtype MaybeT m a = MaybeT { runMaybeT :: m (Maybe a) } instance Monad m => Monad (MaybeT m) where return x = MaybeT (return (Just x)) MaybeT action >>= f = MaybeT $ do result <- action case result of Nothing -> return Nothing Just x -> runMaybeT (f x)
That was easy! I just replaced MaybeIO with MaybeT, IO with m, and added an "m" type parameter (with Monad constraint).
transformToMaybeT :: Monad m => m a -> MaybeT m a transformToMaybeT action = MaybeT $ do result <- action return (Just result)
MaybeT
EitherT
lift
transformToMaybeT :: Monad m => m a -> MaybeT m a transformToEitherT :: Monad m => m a -> EitherT l m a
It seems we can capture this pattern with a class:
class MonadTrans t where lift :: (Monad m) => m a -> t m a
MaybeT
(* -> *) -> * -> *
lift
instance MonadTrans MaybeT where lift = transformToMaybeT
Monad Transformers
(* -> *) -> (* -> *)
* -> *
* -> *
