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 mappending 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
fis being called only with thexargument. So inlogNumber 3 >>= \a -> ...variableaindeed is bound to3.However,
>>=does something after callingf, namely it combinesvwithv'. In your example,vis the[String]coming fromlogNumber 3which is["Got number: 3"]. Insteadv'comes form evaluating\a -> ...witha=3, and is["Got number: 5"].mappendfor lists is++, which concatenates the lists together. Hence we get the final result.Allow me to be a little sloppy and neglect the
Writerwrappers. 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 userunWriterto exit from the monadic context.