Building Applications with Many Effects
So far in this book you’ve learned about different individual Monads, like IO actions, lists of values, and optional values. As you start to write more sophisticated applications, it’s frequently desirable to compose the properties of these individual Monads to build up types with new behaviors that combine the properties of individual monadic actions. This technique is used in a huge number of real world Haskell applications, and it will enable you to read and write real world professional Haskell applications.
In this chapter you’ll learn about how to compose monadic computations using Monad transformers. You’ll learn about some common Monad transformers provided by the MTL and Transformers libraries, and learn how to make use of tagless final encodings to use Monad transformers effectively.
You’ll be able to build new Monads that combine different sorts of monadic computations, like having a read-only environment, dealing with exception handling, and performing IO.
In the next chapter you will learn how to use Monad transformers to create a core application Monad out of an MTL stack, using the Monad transformers that you learn about in this chapter.
Building Out The Monad Transformer Library
In this chapter we focused on two specific Monad transformers: StateT
and
ExceptT
. The transformers
library provides several other commonly used Monad
transformers. One of the most common Monad transformers is the ReaderT
transformer. This Monad transformer lets you write computations that have a
read-only environment:
newtype ReaderT r m a = ReaderT {runReaderT :: r -> m a}
The two basic operations for a ReaderT
Monad are ask
, which fetches the
value from the read-only environment, andlocal
, which lets you run a ReaderT
action with a modified local read-only environment. Their types are:
ask :: Monad m => ReaderT r m r
local :: Monad m => (r -> r) -> ReaderT r m a -> ReaderT r m a
In this exercise, write Functor
, Applicative
, Monad
, MonadIO
, and
MonadTrans
instances for ReaderT
, and provide a definition for both ask
and local
. Once you have created a working definition of ReaderT
, add a new
class called MonadReader
:
class Monad m => MonadReader r m | m -> r where
ask :: m r
local :: (r -> r) -> m a -> m a
Next, finish writing the following instances:
instance Monad m => MonadReader r (Reader.ReaderT r m) where
instance MonadReader r m => MonadReader r (ExceptT e m) where
instance MonadReader r m => MonadReader r (StateT s m) where
Hint 1
Some high level hint text
Hint 2
Some more detailed hint text
Hint 3
Even more detailed hint text
Solution
A complete solution for the exercise
A New FilePackParser
Refactor the FilePackParser
application that you wrote earlier in this book to
use the following Monad:
newtype FilePackParser a = FilePackParser
runFilePackParser :: StateT Text (ExceptT Text IO) a } {
Hint 1
Some high level hint text
Hint 2
Some more detailed hint text
Hint 3
Even more detailed hint text
Solution
A complete solution for the exercise