| Prev: Combining monads the hard way | TOC: Contents | Next: Standard monad transformers |
Monad transformers are special variants of standard monads that facilitate the combining of monads. Their type constructors are parameterized over a monad type constructor, and they produce combined monadic types.
Type constructors play a fundamental role in Haskell's monad support.
Recall that Reader r a is the type of values of type
a within a Reader monad with environment of type r.
The type constructor Reader r is an instance of the
Monad class, and the runReader::(r->a) function
performs a computation in the Reader monad and returns the result of type
a.
A transformer version of the Reader monad, called ReaderT,
exists which adds a monad type constructor as an addition parameter.
ReaderT r m a is the type of values of
the combined monad in which Reader is the base monad
and m is the inner monad.
ReaderT r m is an instance of the monad class,
and the runReaderT::(r -> m a)
function performs a computation in the combined monad and returns a
result of type m a.
Using the transformer versions of the monads, we can produce combined monads
very simply. ReaderT r IO is a combined Reader+IO monad.
We can also generate the non-transformer version of a monad from the
transformer version by applying it to the Identity monad. So
ReaderT r Identity is the same monad as
Reader r.
If your code produces kind errors during compilation, it means that you are
not using the type cosntructors properly. Make sure that you have supplied
the correct number of parameters to the type constructors and that you
have not left out any parenthesis in complex type expressions.
When using combined monads created by the monad transformers, we avoid having to explicitly manage the inner monad types, resulting in clearer, simpler code. Instead of creating additional do-blocks within the computation to manipulate values in the inner monad type, we can use lifting operations to bring functions from the inner monad into the combined monad.
Recall the liftM family of functions which are used to
lift non-monadic functions into a monad. Each monad transformer provides
a lift function that is used to lift a monadic computation
into a combined monad. Many transformers also provide a liftIO
function, which is a version of lift that is optimized for
lifting computations in the IO monad. To see this in action,
we will continue to develop our previous example in the Continuation monad.
| Code available in example21.hs |
|---|
fun :: IO String
fun = (`runContT` return) $ do
n <- liftIO (readLn::IO Int)
str <- callCC $ \exit1 -> do -- define "exit1"
when (n < 10) (exit1 (show n))
let ns = map digitToInt (show (n `div` 2))
n' <- callCC $ \exit2 -> do -- define "exit2"
when ((length ns) < 3) (exit2 (length ns))
when ((length ns) < 5) $ do liftIO $ putStrLn "Enter a number:"
x <- liftIO (readLn::IO Int)
exit2 x
when ((length ns) < 7) $ do let ns' = map intToDigit (reverse ns)
exit1 (dropWhile (=='0') ns') --escape 2 levels
return $ sum ns
return $ "(ns = " ++ (show ns) ++ ") " ++ (show n')
return $ "Answer: " ++ str
|
Compare this function using ContT, the transformer version of
Cont, with the original version to see how unobtrusive the
changes become when using the monad transformer.
| Nested monads from example 19 | Monads combined with a transformer from example 21 |
|---|---|
fun = do n <- (readLn::IO Int)
return $ (`runCont` id) $ do
str <- callCC $ \exit1 -> do
when (n < 10) (exit1 (show n))
let ns = map digitToInt (show (n `div` 2))
n' <- callCC $ \exit2 -> do
when ((length ns) < 3) (exit2 (length ns))
when ((length ns) < 5) (exit2 n)
when ((length ns) < 7) $ do
let ns' = map intToDigit (reverse ns)
exit1 (dropWhile (=='0') ns')
return $ sum ns
return $ "(ns = " ++ (show ns) ++ ") " ++ (show n')
return $ "Answer: " ++ str
|
fun = (`runContT` return) $ do
n <- liftIO (readLn::IO Int)
str <- callCC $ \exit1 -> do
when (n < 10) (exit1 (show n))
let ns = map digitToInt (show (n `div` 2))
n' <- callCC $ \exit2 -> do
when ((length ns) < 3) (exit2 (length ns))
when ((length ns) < 5) $ do
liftIO $ putStrLn "Enter a number:"
x <- liftIO (readLn::IO Int)
exit2 x
when ((length ns) < 7) $ do
let ns' = map intToDigit (reverse ns)
exit1 (dropWhile (=='0') ns')
return $ sum ns
return $ "(ns = " ++ (show ns) ++ ") " ++ (show n')
return $ "Answer: " ++ str
|
The impact of adding the I/O in the middle of the computation is narrowly confined when using the monad transformer. Contrast this with the changes required to achieve the same result using a manually combined monad.
| Prev: Combining monads the hard way | TOC: Contents | Next: Standard monad transformers |