Learn You a Haskell for Great Good! - Miran Lipovaca [138]
Its Monad instance is defined like so:
instance (Monoid w) => Monad (Writer w) where
return x = Writer (x, mempty)
(Writer (x, v)) >>= f = let (Writer (y, v')) = f x in Writer (y, v `mappend` v')
First, let’s examine >>=. Its implementation is essentially the same as applyLog, only now that our tuple is wrapped in the Writer newtype, we need to unwrap it when pattern matching. We take the value x and apply the function f to it. This gives us gives us a Writer w a value, and we use a let expression to pattern match on it. We present y as the new result and use mappend to combine the old monoid value with the new one. We pack that up with the result value in a tuple and then wrap that with the Writer constructor so that our result is a Writer value, instead of just an unwrapped tuple.
So, what about return? It must take a value and put it in a default minimal context that still presents that value as the result. What would such a context be for Writer values? If we want the accompanying monoid value to affect other monoid values as little as possible, it makes sense to use mempty.
mempty is used to present identity monoid values, such as "" and Sum 0 and empty bytestrings. Whenever we use mappend between mempty and some other monoid value, the result is that other monoid value. So, if we use return to make a Writer value and then use >>= to feed that value to a function, the resulting monoid value will be only what the function returns.
Let’s use return on the number 3 a bunch of times, pairing it with a different monoid each time:
ghci> runWriter (return 3 :: Writer String Int)
(3,"")
ghci> runWriter (return 3 :: Writer (Sum Int) Int)
(3,Sum {getSum = 0})
ghci> runWriter (return 3 :: Writer (Product Int) Int)
(3,Product {getProduct = 1})
Because Writer doesn’t have a Show instance, we used runWriter to convert our Writer values to normal tuples that can be shown. For String, the monoid value is the empty string. With Sum, it’s 0, because if we add 0 to something, that something stays the same. For Product, the identity is 1.
The Writer instance doesn’t feature an implementation for fail, so if a pattern match fails in do notation, error is called.
Using do Notation with Writer
Now that we have a Monad instance, we’re free to use do notation for Writer values. It’s handy when we have several Writer values and want to do stuff with them. As with other monads, we can treat them as normal values, and the context gets taken care of for us. In this case, all the monoid values that come attached are mappended, and so are reflected in the final result.
Here’s a simple example of using do notation with Writer to multiply two numbers:
import Control.Monad.Writer
logNumber :: Int -> Writer [String] Int
logNumber x = writer (x, ["Got number: " ++ show x])
multWithLog :: Writer [String] Int multWithLog = do
a <- logNumber 3
b <- logNumber 5
return (a*b)
logNumber takes a number and makes a Writer value out of it. Notice how we used the writer function to construct a Writer value, instead of directly using the Writer value constructor. For the monoid, we use a list of strings, and we equip the number with a singleton list that just says that we have that number. multWithLog is a Writer value that multiplies 3 and 5 and makes sure that their attached logs are included in the final log. We use return to present a*b as the result. Because return just takes something and puts it in a minimal context, we can be sure that it won’t add anything to the log.
Here’s what we see if we run this code:
ghci> runWriter multWithLog
(15,["Got number: 3","Got number: 5"])
Sometimes, we just want some monoid value to be included at some particular point. For this, the tell function is useful. It’s part of the MonadWriter type class. In the case of Writer, it takes a monoid value, like ["This is going on"], and creates a Writer value that presents the dummy value () as its result, but has the desired monoid value attached. When we have a monadic value that has () as its result,