Writer Monad
applyLog :: (a,String) -> (a -> (b,String)) -> (b,String)
applyLog (x,log) f = let (y,newLog) = f x in (y,log ++ newLog)
applyLog :: (Monoid m) => (a,m) -> (a -> (b,m)) -> (b,m)一直追加日志, 跟踪记录。
applyLog (x,log) f = let (y,newLog) = f x in (y,log `mappend` newLog)
Control.Monad.Writer
这模块含有 Writer w a
的一个型态,里面定义了他 Monad
的 instance,还有一些操作这些值的函数。
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')
要把一个 monoid 附加给一个值,只需要定义一个 tuple . 用 monoid w 来 append log。
>>= 就是 applyLog
ghci> runWriter (return 3 :: Writer String Int)用
(3,"")
do
串接
Writer
型态的值
import Control.Monad.Writer
logNumber :: Int -> Writer [String] Int
logNumber x = Writer (x, ["Got number: " ++ show x])
multWithLog :: Writer [String] Int
multWithLog = do
a <- logNumber 3
b <- logNumber 5
return (a*b)
有时候我们就是想要在某个时间点放进某个 Monoid value。tell
正是我们需要的函数。他实作了MonadWriter
这个 type class,而且在当
Writer
用的时候也能接受一个 monoid value,好比说["This is going on"]
。
我们能用他来把我们的 monoid value 接到任何一个 dummy value ()
上来形成一个 Writer。当我们拿到的结果是 ()
的时候,我们不会把他绑定到变量上。来看一个
multWithLog
的范例:
multWithLog :: Writer [String] Int
multWithLog = do
a <- logNumber 3
b <- logNumber 5
tell ["Gonna multiply these two"]
return (a*b)
ghci> runWriter multWithLog
(15,["Got number: 3","Got number: 5","Gonna multiply these two"])
return (a*b)
是我们的最后一行,还记得在一个
do
中的最后一行代表整个
do
的结果。如果我们把
tell
摆到最后,则
do
的结果则会是
()
。我们会因此丢掉乘法运算的结果。除此之外,log 的结果是不变的。
[] 的 连续 ++ 操作, right associative 比较有效率, left associative 没有效率。
当我们用 ++
来实现 appending 的时候,他必须要走到左边的 list 的尾端,然后把右边的 list 一个个从这边接上。
instance Monoid (DiffList a) where
mempty = DiffList (\xs -> [] ++ xs)
(DiffList f) `mappend` (DiffList g) = DiffList (\xs -> f (g xs))
Difference list 最酷的地方在于他支持高效的 appending。当我们用 ++
来实现 appending 的时候,他必须要走到左边
的 list 的尾端,然后把右边的 list 一个个从这边接上。那 difference list 是怎么作的呢?appending 两个 difference list
就像这样
f `append` g = \xs -> f (g xs)
f
跟 g
这边是两个函数,他们都接受一个 list 并 prepend 另一串 list。举例来说,如果 f
代表 ("dog"++)
(可以写成
\xs -> "dog" ++ xs
)而 g
是 ("meat"++)
,那 f `append` g
就会做成一个新的函数,
等价于:
\xs -> "dog" ++ ("meat" ++ xs)
append 两个 difference list 其实就是用一个函数,这函数先喂一个 list 给第一个 difference list,然后再把结果喂给第二个
difference list。