Understanding example on Writer Monad

2k Views Asked by At

I am learning about the Writer Monad on the book Learn You A Haskell.

this is the piece of code:

import Control.Monad.Writer

logNumber :: Int -> Writer [String] Int
logNumber num = writer (num, ["Got number: " ++ show num])

multWithLog :: Writer [String] Int
multWithLog = do
  a <- logNumber 3
  b <- logNumber 5
  return (a * b)

When running multWithLog, this is the result:

*Main> runWriter multWithLog
(15,["Got number: 3","Got number: 5"])

On this lines:

a <- logNumber 3
b <- logNumber 5

It is easy to see that a = 3 and b = 5, since both of them are being multiplied on the return function.

What I don't understand is why those values are 3 and 5. Shouldn't a and b be the values that contain inside the Writer Monad? In this case tuples?

For example, with this was with the Maybe Monad, a and bwould be 3 and 5:

do
  a <- Just 3
  b <- Just 5
  return (a * b)

In this case it makes sense to me, since a and b receive the content inside Just. But with the initial example, a and b only receive part of the value.

3

There are 3 best solutions below

1
On BEST ANSWER

It is easy to see that a = 3 and b = 5, since both of them are being multiplied on the return function. What I don't understand is why those values are 3 and 5. Shouldn't a and b be the values that contain inside the Writer Monad? In this case tuples?

No. I think the simplest way to answer this is to just implement the Writer type and study its Monad class instance:

newtype Writer w a = Writer { runWriter :: (a, w) }

instance Functor (Writer w) where
  fmap f (Writer (a, w)) = Writer (f a, w)

instance Monoid w => Applicative (Writer w) where
  pure a = Writer (a, mempty)
  Writer (f, w) <*> Writer (a, w') = Writer (f a, w <> w')

instance Monoid w => Monad (Writer w) where
  return = pure
  Writer (a, w) >>= f = let (b, w') = runWriter (f a)
                        in Writer (b, w <> w')

tell :: w -> Writer w ()
tell w = Writer ((), w)

As you can see in the instance method for >>=, the function f is applied to the a value, not the whole tuple. The syntax a <- logNumber 3 is desugared using >>=, so the value that's bound to a will be the first element of the tuple that Writer wraps around.

0
On

The idea of “values contained in a monad” is a bit fuzzy. It kind of works, but not rigorously.

Really, a monad as such doesn't “contain” anything. It's not actually a spacesuit or burrito, you know...

In the case of Writer, you can say clearly that this is a type whose values contain both log-snippets and computation-results. The latter is what you might interpret as the “content” of the monad, and is what you can retrieve with a <-. But in general, there needn't be any such content at all.

In fact, to stay with your Maybe example:

do a <- Just 3
   Nothing
   b <- Just 5
   return (a * b)

In this case, the Nothing “interrupts” the computation, so the Just 5 doesn't ever get to inject the value 5 into a.

The thing to keep in mind is that, if action :: M T for some monad M, then

do
   ...
   a <- action

can give a a value of type T. (In your example, M is Writer [String] and T is Int, so a can only have type Int.)

Whether that actually happens, how often, where the values come from and what it means depends on the particular monad. Still, the monad laws can tell you a lot about the whole computation will behave. But while learning about monads, it's probably best to forget about all this, simply look at many different examples and dabble with monads yourself, at some point you'll get an intuition for them. Don't try too hard to “understand” monads through analogies.

0
On

In this case it makes sense to me, since a and b receive the content inside Just. But with the initial example, a and b only receive part of the value.

There is nothing really wrong with this description, but we can also look at this situation in another way. In...

  a <- Just 3

... we might also say that a receives only part of Just 3 -- the Just wrapper does not make it. From that point of view, what happens with...

  a <- writer (3, ["Got number: " ++ show 3])

... is quite similar: a only receives the 3 value. The [String] annotations, so to say, stay in the background, getting mappended into each other by the monadic binds, much like (>>=) combines Maybes so that Just with Just gives Just and Nothing with anything gives Nothing.