Learn You a Haskell for Great Good 读书笔记 13

Monads

Functor —-> Applicative —-> Monad

直接上公式:

1
(>>=) :: (Monad m) => m a -> (a -> m b) -> m b

这个函数叫bind

它接受一个Monad 和 一个将参数包成Monad的函数,返回Monad,其中的值经过变换。

举个Maybe的例子

Maybe as Monads

1
2
3
applyMaybe :: Maybe a -> (a -> Maybe b) -> Maybe b
applyMabye Nothing f = Nothing
applyMaybe (Just x) f = f x

Monads 类型类

1
2
3
4
5
6
7
8
class Monad m where
return :: a -> m a
(>>=) :: m a -> (a -> m b) -> m b
(>>) :: m a -> m b -> m b
x >> y = x >>= \_ -> y

fail :: String -> m a
fail msg = error msg

理论上Monad应该继承Applicative类型对吧,然后并没有。因为Monad被想出来的时候Applicative还没出现。

return 和 Applicative里的pure差不多,都是把类型包一下

其他精华就在那个bind函数

再看看Maybe的Monad实际定义:

1
2
3
4
5
instance Monad Maybe where
return x = Just x
Nothing >>= f = Nothing
Just x >>= f = f x
fail _ = Nothing

do

do 代码块下不仅可以把IO Action操作的数拿出来耍,它本质上是对Monad chain的语法简化:

1
2
3
4
5
6
7
8
Just 3 >>= (\x -> Just "!" >>= (\y -> Just $ show x ++ y))
等价于
let x = 3, y = "1" in show x ++ y
等价于
foo = do
x <- Just 3
y <- Just "!"
return $ show x ++ y

do 代码块看上去像命令式的代码,其实是Monad函数链!

1
2
3
4
5
6
7
dead :: Maybe String
dead = do
start <- Just "begin"
first <- Just $ start ++ " first"
Nothing
second <- Just $ first ++ " second"
return second

上面的dead最后等于Nothing。单行一个Monad值,相当于在链中加入>> theMonad

所以等价于:

1
2
> Just "begin" >>= \x -> Just (x ++ " first") >> Nothing >>= \x -> Just $ x ++ " second"
> Nothing

异常处理

monad 链里模板匹配抛异常了,monad就把 fail msg 的结果作为这一环的结果,继续下去。可以看到Maybe就直接返回Nothing,那么这个链最后的值只能是Nothing了。

List as Monad

1
2
3
4
instance Monad [] where 
return x = [x]
xs >>= f = concat (map f xs)
fail _ = []

可以看到 >>=它 concat了一下,因为 >>=的第二个参数 a -> m b会把数组里一个元素又包成一个List,所以直接concat每个元素产生的list,感觉这就是flatMap的原型。

记不记得List comprehension里面也出现过<-这个符号,其实通过Monad和do就通了:

1
2
3
4
5
6
7
8
[(n, ch) | n <- [1, 2], ch <- ['a', 'b']]
--等价于
listOfTuples = do
n <- [1, 2]
ch <- ['a', 'b']
return (n, ch)
--等价于
[1, 2] >>= \n -> ['a', 'b'] >>= \ch -> return (n, ch)

List comprehension里还有一个filter:

1
2
> [x | x <- [1..50], '7' `elem` show x]
> [7,17,27,37,47]

它用Monad的函数要怎么表示呢,原有的就不够了,需要混合Monad和Moniod。新的类型类叫MonadPlus:

1
2
3
4
5
6
7
class Monad m => MonadPlus m where
mzero :: m a
mplus :: m a -> m a -> m a

instance MonadPlus [] where
mzero = []
mplus = (++)

然后我们定义一个guard函数:

1
2
3
guard :: (MonadPlus m) => bool -> m ()
guard True = return ()
guard False = mzero

然后就通了:

1
> [1..50] >>= (\x -> gurad ('7' `elem` show x) >> return x)

gurad True的时候返回[()],空Tuple可以代表任一类型,所以遇到 >>时直接返回函数后的值,即[x]被保留下来;guard False 时返回[],类似fail msg的值,[] 在List concat时被忽略。

Monad 定律

  1. left identify: return x >>= f 等价于 return $ f x
  2. right identify: m x >>= retrun 等于 m x
  3. Associativity: (m >>= f) >>= g 等价于 m >>= \x -> f x >>= g