The package Control.Monad.Writer
does not export the data constructor Writer
. I guess this was different when LYAH was written.
Using the MonadWriter typeclass in ghci
Instead, you create writers using the writer
function. For example, in a ghci session I can do
ghci> import Control.Monad.Writer
ghci> let logNumber x = writer (x, ["Got number: " ++ show x])
Now logNumber
is a function that creates writers. I can ask for its type:
ghci> :t logNumber
logNumber :: (Show a, MonadWriter [String] m) => a -> m a
Which tells me that the inferred type is not a function that returns a particular writer, but rather anything that implements the MonadWriter
type class. I can now use it:
ghci> let multWithLog = do { a <- logNumber 3; b <- logNumber 5; return (a*b) }
:: Writer [String] Int
(Input actually entered all on one line). Here I've specified the type of multWithLog
to be Writer [String] Int
. Now I can run it:
ghci> runWriter multWithLog
(15, ["Got number: 3","Got number: 5"])
And you see that we log all of the intermediate operations.
Why is the code written like this?
Why bother to create the MonadWriter
type class at all? The reason is to do with monad transformers. As you correctly realised, the simplest way to implement Writer
is as a newtype wrapper on top of a pair:
newtype Writer w a = Writer { runWriter :: (a,w) }
You can declare a monad instance for this, and then write the function
tell :: Monoid w => w -> Writer w ()
which simply logs its input. Now suppose you want a monad that has logging capabilities, but also does something else - say it can read from an environment too. You'd implement this as
type RW r w a = ReaderT r (Writer w a)
Now because the writer is inside the ReaderT
monad transformer, if you want to log output you can't use tell w
(because that only operates with unwrapped writers) but you have to use lift $ tell w
, which "lifts" the tell
function through the ReaderT
so that it can access the inner writer monad. If you wanted two layers transformers (say you wanted to add error handling as well) then you'd need to use lift $ lift $ tell w
. This quickly gets unwieldy.
Instead, by defining a type class we can make any monad transformer wrapper around a writer into an instance of writer itself. For example,
instance (Monoid w, MonadWriter w m) => MonadWriter w (ReaderT r m)
that is, if w
is a monoid, and m
is a MonadWriter w
, then ReaderT r m
is also a MonadWriter w
. This means that we can use the tell
function directly on the transformed monad, without having to bother with explicitly lifting it through the monad transformer.