7

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?

Will Ness
  • 70,110
  • 9
  • 98
  • 181
Enlico
  • 23,259
  • 6
  • 48
  • 102
  • 1
    If you remove the `return (x*y)` line, then this will indeed not compile, since then `()` would be the result. I do not really see what problem you face with the quoted paragraph? – Willem Van Onsem Jun 03 '20 at 20:22
  • For one thing it ends with _the log would be the same_. Which log, if the code does not even compile? – Enlico Jun 03 '20 at 20:24
  • 1
    well the author probably assumes that you change the signature to `Writer [String] ()` :). In that case the result will be `()` but it will still write `["something"]` to the log. – Willem Van Onsem Jun 03 '20 at 20:25
  • That edge case is the only one that allows the code to compile. But if that is the signature, then the example totally loses its meaning... – Enlico Jun 03 '20 at 20:28
  • 1
    well that is exactly the point of the author. That the last line is important, since otherwise the return value (and here type as well) is different. – Willem Van Onsem Jun 03 '20 at 20:29
  • 1
    If anyone reading this wants to play with this LYAH example and see if it still compiles, try the Jupyter adaptation of LYAH (chapter 13) https://github.com/jamesdbrock/learn-you-a-haskell-notebook – James Brock Jun 04 '20 at 05:53

1 Answers1

5

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".

Will Ness
  • 70,110
  • 9
  • 98
  • 181