Monad: Writer
This summary follows the minimum useable principle.
Readings
- Yahtee1
- Yahtee2
haskell at work: domain modeling
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
- Target type :
a - Context type :
(Monoid m) => Writer mor(Monoid m) => WriterT w m- Explicitly : transformation among
target types :: a -> b -> c. - Implicitly : Aggregate
logginginformation of typewduring the target types transformation.
- Explicitly : transformation among
- There is a type
b.- There is a function (
:: a -> b) fromatob.- Many functions
a -> b,b -> c…y -> zmany compose as a transformation froma -> z.- Each function many produce some extra
logginginformation of typew.- The product of two types
wandbis(b,w).- We want the result of this transformation
a -> z, we also want to aggregate thelogginginformation of each function in this transformation.- We define a new type
newtype Writer w a = Writer {runWriter :: (a,w)}- In
Writer Monadwe 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 typesandlogginginformation are being processed explicitly and implicitly respectively.
- Auxiliary functions
tell,return,listen(s),pass,censor - These functions output Writer Monad.
- These special purpose Writer Monads are composed by
>>operator usually. - They collectively work as a single Writer Monad for certain function.
wrtier
liftandwrapa type product(a,w)to be aWriterTmonad.writer :: (Monad m) => (a, w) -> WriterT w m a writer = WriterT . returntell
Introduce the logging information
wtell :: (Monad m) => w -> WriterT w m () tell w = writer ((), w)
return
Introduce the value of
target type: ainstance (Monoid w, Monad m) => Monad (WriterT w m) where return a = writer (a, mempty)Usually,
tellandreturnare being used together in aWriter Monad:
>.... tell w >> return atell wintroduce the logging information andreturn aintroduce thetarget information,>>combines these two together as a newWriter Monad.
listen
Retrieve logging information
wby transformingtarget typeato(a,w). So that we could processlogging information wexplicitly.listen :: (Monad m) => WriterT w m a -> WriterT w m (a, w) listen m = WriterT $ do ~(a, w) <- runWriterT m return ((a, w), w)listens
Retrieve logging information
wfromWriter Monadjust like whatlistendoes. Only apply a function of typew -> bon the `logging information.listens :: (Monad m) => (w -> b) -> WriterT w m a -> WriterT w m (a, b) listens f m = WriterT $ do ~(a, w) <- runWriterT m return ((a, f w), w)censor
Transform existed
winWriter Monadwith a function of typew -> w.censor :: (Monad m) => (w -> w) -> WriterT w m a -> WriterT w m a censor f m = WriterT $ do ~(a, w) <- runWriterT m return (a, f w)pass
If our target type contains a function of type
w -> win this form(a, w->w).passpreserveain target type and apply the functionw -> win target type to the logging informationwin context type.pass :: (Monad m) => WriterT w m (a, w -> w) -> WriterT w m a pass m = WriterT $ do ~((a, f), w) <- runWriterT m return (a, f w)####Section Summary >Haskell enable us to decompose an application into
target computationandcomputation context. So that we could manage to get predictable outcome by recomposing varioustarget computationwithcomputation context. These auxiliary function mainly about manipulatingContextrelated 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")
Double :: (10+0.2) * 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:
- Target Operation:
p1listandp2listzip together produces the target input of type[(Int,Int)].- The target transform is
(Int,Int) -> Int. Which iss = e1 + e2. Usually we use
maporfmap (<$>)to lift this transform so it works in the[]container.[(1,2),(2,4),(3,6)...(10,20)] -> [3,5,9,...,30] fmap (Int,Int) -> Int [...]
- Context Semantics:
- We want to log some information for each
target transformof the element. - Accumulate the sum of the first list and second list respectively.
- So define
LoggingTypeas the instance ofSemigroupandMonoid. The logging information is
LoggingType e1 e2.[(1,2),(2,4),(3,6)...(10,20)] -> [3,5,9,...,30] mapM (Int,Int) -> Writer LoggingType Int [...]
- We want to log some information for each
- This newly Context sensitive transform
createLogneed to cooperate withmapMinstead ofmap. The
Target ComputationandComputation Contextwould more clear if we rewritecreateLogas:createLog (e1,e2) = do tell $ LoggingType e1 e2 -- tell : Context Operation :: LoggingType pure $ e1 + e2 -- pure : Target Operation :: Intpureis always about bring value oftarget typeinto thisComputation Context