haskellmonadsdo-notationrewritingwriter-monad

LYAH - Understanding comment about "tell" when chaining Writer monads


Question is in bold at the bottom.

LYAH gives this example of using the do notation with the Writer monad

import Control.Monad.Writer

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

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

where the definition can be re-written without the do notation:

multWithLog = logNumber 3 >>= (\x ->
              logNumber 5 >>= (\y ->
              return (x*y)))

So far so good.

After that, the book introduces tell, and edits the definition of multWithLog like this:

multWithLog = do
              a <- logNumber 3
              b <- logNumber 5
              tell ["something"]
              return (x*y)

which again can be rewritten as:

multWithLog = logNumber 3 >>= (\x ->
              logNumber 5 >>= (\y ->
              tell ["something"] >>
              return (x*y)))

Then the book makes the a point which appears unclear to me, if not inaccurate:

It's important that return (a*b) is the last line, because the result of the last line in a do expression is the result of the whole do expression. Had we put tell as the last line, () would have been the result of this do expression. We'd lose the result of the multiplication. However, the log would be the same.

Therefore, here my first doubt comes: if tell results in (), then the code should not and does not even compile, as the () cannot match the expected type Int, nor any other type other than () itself; so what is the author trying to tell us? To make this non-opinion-based, has something changed in Haskell, since the book was written, that made the above quoted statement unclear/inaccurate?


Solution

  • The equivalent re-write is further

    multWithLog = logNumber 3        >>= (\ x ->
                  logNumber 5        >>= (\ y ->
                  tell ["something"] >>= (\ () ->     -- () NB
                  return (x*y)       >>= (\ result ->
                  return result ))))
    

    and that is the () that tell ["something"] "returns". Obviously, the shuffled

    multWithLog2 = logNumber 3        >>= (\ x ->
                   logNumber 5        >>= (\ y ->
                   return (x*y)       >>= (\ result ->
                   tell ["something"] >>= (\ () ->
                   return () ))))
    

    would indeed have the type Writer [String] (), so if the signature were to specify Writer [String] Int, it would indeed not compile.

    Sans the type signature issues, the "log" i.e. the gathered [String] list would be the same with both variants, as return does not alter the collected output "log".