I'm learning Haskell from the "Learn you a Haskell for Great Good" tutorial and I've got to the part on writer monads. Here's the example that I can't figure out.
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) -- shouldn't return (3*5) result in (15,[]) ?
ghci> runWriter $ multWithLog
(15,["Got number: 3","Got number: 5"]) -- how did that happen?
I am trying understand how the monoidic value w
in the Writer w a
monad returned by the do
block got changed. The tutorial did not go into details on how the mappend
ing took place.
The type declaration for Writer
and the instance declaration for Writer
as a monad is given by the tutorial as
newtype Writer w a = Writer { runWriter :: (a, w) }
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')
if return x
results in Writer (x, mempty)
as per the instance declaration and mempty
for monoid [a]
is []
, shouldn't return (a*b)
, which amounts to return (3*5)
, evaluate to (15,[])
?
ghci> return (15) :: Writer [String] Int
WriterT (Identity (15,[]))
I gave the above command to ghci and it returns a WriterT
type value, the tuple contains an empty list as expected.
multWithLog :: Writer [String] Int
multWithLog = logNumber 3 >>= (\a ->
logNumber 5 >>= (\b ->
return (a*b)))
I've rewritten the do
block using bind operators instead. The above code gave identical result as the original code in the tutorial.
I'm under the impression that >>=
only extracted Int
3
from the result of logNumber 3
and gave it to (\a -> logNumber 5 ...etc.)
, which then did the logNumber
function on a different value (5
) and so on. How did these operations lead to the [String]
part of the Writer monad being altered?
From the code you posted
we can see that indeed
f
is being called only with thex
argument. So inlogNumber 3 >>= \a -> ...
variablea
indeed is bound to3
.However,
>>=
does something after callingf
, namely it combinesv
withv'
. In your example,v
is the[String]
coming fromlogNumber 3
which is["Got number: 3"]
. Insteadv'
comes form evaluating\a -> ...
witha=3
, and is["Got number: 5"]
.mappend
for lists is++
, which concatenates the lists together. Hence we get the final result.Allow me to be a little sloppy and neglect the
Writer
wrappers. We getIntuitively, we can pretend that a value in your writer monad is an effectful computation, which returns a value (like
3
) and as a side effect appends a few messages to a list-of-strings. The log of all such messages is invisible inside the monad (we can only append to the log), and will only be made available at the very end, when we will userunWriter
to exit from the monadic context.