More Monad
Writer
我们想在程序运行的时候打log怎么办? 用(a, String)去记录结果,那如何把一连串的log连起来而不是只保留一步的log呢? 用Monad, 然后用++把String连起来:
1 | applyLog :: (a, String) -> (a -> (b, String)) -> (b, String) |
这里对String可以作进一步的抽象。String其实就是[Char],而(++)对应是Monoid里的mappend,那String可以抽象成Monoid,即任何可以foldable的类型。
1 | applyLog :: (Monoid m) => (a, m) -> (a -> (b, m)) -> (b, m) |
当我们做了这一层的抽象后,显然log就不能代表这个monad的全部功能了,因为monoid可不止List, 还有Sum等,于是我们有了Writer:
1 | newType Writer w a = Writer { runWriter :: (a, w) } |
注意由上推出的>>变得有意思了,monad包含的值会取运算符后面的monad的值,而monad中tuple包含的monoid则是经过mappend组合在了一起:
1 | (Writer (x, v)) >> (Writer (y, v')) = Writer (x, v) >>= \n -> Writer (y, v') |
?? tell函数,还没搞明白。
1 | > tell :: Writer w m => w -> m () |
接下来,变魔术:
1 | multWithLog :: Writer [String] Int |
1 | > runWriter multWithLog |
上面表达式用>>=显式来写:
1 | multWithLog = logNum 3 >>= \x -> logNum 5 >>= \y -> tell ["Gonna mul them"] >> return $ x*y |
可以看到,你好像没有做任何组装log的动作,表达式里也没有任何痕迹,但最后log就带出来了。关键在Writer>>=的定义。
给程序打log
有了Writer就可以给你的函数打log了,比如:
1 | import Control.Monad.Writer |
运行:
1 | > fst $ runWriter $ gcd' 8 3 |
只要把返回类型包一层Writer, 程序内换成do代码块或者>>=就行了。
函数作为Monad
老套路,慢慢推
1 | (->) r a -> (a -> (->) r b) -> (->) r b |
另一个return比较简单,就不推了。
1 | instance Monad ((->) r) where |