4

I have a question regarding a block of code from th Week04 Plutus Pioneer Program. The block is in Writer.hs

foo'' :: Writer Int -> Writer Int -> Writer Int -> Writer Int
foo'' x y z = do
    s <- threeInts x y z
    tell ["sum: " ++ show s]
    return s

For reference,

threeInts :: Monad m => m Int -> m Int -> m Int -> m Int
threeInts mx my mz =
mx >>= \k ->
my >>= \l ->
mz >>= \m ->
let s = k + l + m in return s

tell :: [String] -> Writer ()
tell = Writer ()

instance Monad Writer where
    return a = Writer a []

number :: Int -> Writer Int
number n = Writer n $ ["number: " ++ show n]

Why does

foo'' (number 2) (number 3) (number 4)

return

Writer 9 ["number: 2","number: 3","number: 4","sum: 9"]

and not

Writer 9 []

Obviously there are holes in my understanding, but I read the do block as follows:

  • The result of threeInts x y z is Writer 9 ["number: 2","number: 3","number: 4"]
  • 9 is extracted from this to the definition s
  • tell ["sum: " ++ show s] results in Writer () ["sum: 9"]
  • return s results in Writer 9 []

I know that return in Haskell is not the same as it is in an imperative language, but I'm failing miserably to grok how to read this block effectively. The lambda version producing the same result is more explicit and makes sense to me.

bindWriter :: Writer a -> (a -> Writer b) -> Writer b
bindWriter (Writer a xs) f =
  let
    Writer b ys = f a
  in
    Writer b $ xs ++ ys

foo' :: Writer Int -> Writer Int -> Writer Int -> Writer Int
foo' x y z = x `bindWriter` \k ->
             y `bindWriter` \l ->
             z `bindWriter` \m ->
             let s = k + l + m
             in tell ["sum: " ++ show s] `bindWriter` \_ ->
                Writer s []

I read this backwards where (for my concrete example) Writer 9 [] is bound/combined with Writer () ["sum: 9"] which is then bound/combined with Writer 4 ["number: 4"], Writer 3 ["number: 3"] and Writer 2 ["number: 2"] where the Int values are discarded in the bind process and the "logs" are concatenated.

There is something implicit in the do notation that I'm overlooking or not reading correctly. Any help appreciated.

1 Answer 1

4

The 'secret' is in the bindWriter function that drive the Writer monad.

instance Monad Writer where
    return a = Writer a []
    (>>=) = bindWriter

It is the bindWriter function that aggregates the string list of the Writers and this happens as part of the execution of the do block which is a list of monadic statements.

For instance, comment out the tell line and the do block in foo'' returns

Writer 9 ["number: 2","number: 3","number: 4"]

Rewriting the do block to

foo'' x y z = do
     threeInts x y z

returns the same result. Without return the do block returns the result of the last line (and the compiler complains when it isn't of the right type).

Adding a Writer line returns the Int from the Writer, but the full list of strings:

foo'' x y z = do
     threeInts x y z
     Writer 0 ["adding 0"]

return

Writer 0 ["number: 1","number: 2","number: 3","adding 0"]

The tell function causes a string list to be added ignoring the Int part. The threeInts result needs to be captured because it contains the Int result that needs to be returned (and the tell line needs it for the text line).

This monad allows the writing of functions that operate on the Ints and that can emit appropriate messages that are handled in the background.

1
  • This is what I was missing. Much appreciated!
    – Phil Boltt
    Commented Jul 31, 2021 at 21:38

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.