Hao Wu / Field Notes

Monad: Writer

This summary follows the minimum useable principle.

Readings

newtype WriterT w m a = WriterT { runWriterT :: m (a, w) }

Simple definition from learn you a haskell

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

instance (Monoid w) => Monad (Writer w) where  
    return x = Writer (x, mempty)  
    (Writer (x,v)) >>= f = let (Writer (y, v')) = f x in Writer (y, v `mappend` v') 

Complete definition from Control.Monad.Trans

newtype WriterT w m a = WriterT { runWriterT :: m (a, w) }

instance (Monoid w, Monad m) => Monad (WriterT w m) where
    return a = writer (a, mempty)
    m >>= k  = WriterT $ do
        ~(a, w)  <- runWriterT m
        ~(b, w') <- runWriterT (k a)
        return (b, w `mappend` w')

Monadic Semantics

  • There is a type b.
  • There is a function (:: a -> b) from a to b.
  • Many functions a -> b , b -> cy -> z many compose as a transformation from a -> z.
  • Each function many produce some extra logging information of type w.
  • The product of two types w and b is (b,w).
  • We want the result of this transformation a -> z, we also want to aggregate the logging information of each function in this transformation.
  • We define a new type newtype Writer w a = Writer {runWriter :: (a,w)}
  • In Writer Monad we care about computation compositions :
  • >>= :: Writer w a -> (a -> Writer w b) -> Writer w b.
  • >=> :: (a -> Writer w b) -> ( b -> Writer w c) -> (a -> Writer w c)
  • The transformation of target types and logging information are being processed explicitly and implicitly respectively.

wrtier

tell

return

listen

listens

censor

pass

####Section Summary

Haskell enable us to decompose an application into target computation and computation context. So that we could manage to get predictable outcome by recomposing various target computation with computation context. These auxiliary function mainly about manipulating Context related information.

Example 1: Simple Example

necessary imports:

import Control.Monad.Trans.Writer
import GHC.Float

Code:

f1 :: Int -> Writer String Float
f1 i = do
  tell $ show i
  return $ fromIntegral i + 0.2

f2 :: Float -> Writer String Double
f2 f = do
  tell $ show f
  return $ float2Double f * 2

Check in ghci:

> :info f1
f1 :: Int -> Writer String Float
> :info f2
f2 :: Float -> Writer String Double
> import Control.Monad    -- for the ( >=> ) operator
> let ff = f1 >=>f2
> :info ff
ff :: Int -> WriterT String Data.Functor.Identity.Identity Double
> let r = runWriter $ ff 10
> :info r
r :: (Double, String) 	-- Defined at <interactive>:20:5
> r
(20.399999618530273,"1010.2")

Example 2: Real World Simple Example

necessary import:

import Data.Traversable

Code:

data LoggingType = LoggingType
  {
    partOne :: Int
  , partTwo :: Int
  }deriving (Show,Eq)

instance Semigroup LoggingType where
  (LoggingType o1 t1) <> (LoggingType o2 t2) = LoggingType (o1 + o2) (t1 + t2)

instance Monoid LoggingType where
  mempty = LoggingType 0 0

p1list = [1..10]
p2list = [2,4..40]

logList = uncurry LoggingType <$> zip p1list p2list

createLog :: (Int,Int) -> Writer LoggingType Int
createLog (e1, e2) =
  let w = LoggingType e1 e2
      s = e1 + e2
  in writer (s,w)

totalLog :: Writer LoggingType [Int]
totalLog = mapM createLog $ zip p1list p2list

Check in ghci:

> totalLog 
WriterT (Identity ([3,6,9,12,15,18,21,24,27,30],LoggingType {partOne = 55, partTwo = 110}))

Intuition: