LYAH - Understanding comment about "tell" when chaining Writer monads
Asked Answered
E

1

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?

Ephemeris answered 3/6, 2020 at 20:19 Comment(6)
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?Faeroese
For one thing it ends with the log would be the same. Which log, if the code does not even compile?Ephemeris
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.Faeroese
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...Ephemeris
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.Faeroese
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) github.com/jamesdbrock/learn-you-a-haskell-notebookBroadcloth
S
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".

Strawflower answered 3/6, 2020 at 20:27 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.