内容简介:monad 是加强版的 applicative 函子。我现在有点忘了什么是 applicative 函子,所以我先复习一下。支持 fmap 的类型就是函子,比如 Maybe fmap 的定义是
monad 是加强版的 applicative 函子。
我现在有点忘了什么是 applicative 函子,所以我先复习一下。
复习
函子(Functor 类型类)
支持 fmap 的类型就是函子,比如 Maybe fmap 的定义是 (a -> b) -> f a -> f b
其意义是把一个容器里的值,经过一个函数加工一下,然后放回一样的容器里。
Applicative 类型类
支持 pure 和 <*>
的类型就是 applicative 的实例,比如 Maybe pure 的定义是 a -> f a
,其作用是把一个值装进容器里 <*>
的定义是 f (a -> b) -> f a -> f b
其意义跟 fmap 很相似,区别在于那个函数也在容器里。
但是有些时候容器比喻并不那么有用,比如「(->) r 也是 Application 的实例」那里。
换个角度再说一遍
假设一,我们有
- 类型为 A 的普通值
- 普通函数 A -> B
- 想得到类型为 B 的返回值
那么用直接调用普通函数即可。
假设二,我们有
- 类型为 Maybe A 的奇特值
- 普通函数 A -> B
- 想得到类型为 Maybe B 的返回值
那么我们就要求 Maybe 满足 fmap 的条件,然后调用 fmap 和普通函数
ghci> fmap (++ "!") (Just "wisdom") Just "wisdom!" ghci> fmap (++ "!") Nothing Nothing 复制代码
假设三,我们有
- 类型为 Maybe A 的奇特值
- 奇特函数 Maybe (A -> B)
- 想得到类型为 Maybe B 的返回值
那么我们就要求 Maybe 满足 pure 和 <*>
,然后调用 <*>
和奇特函数
ghci> Just (+3) *> Just 3 Just 6 ghci> Nothing <*> Just "greed" Nothing ghci> Just ord <*> Nothing Nothing -- pure 的意义是让普通函数也能用 -- max <$> 等价于 pure max <*> ghci> max <$> Just 3 <*> Just6 Just 6 ghci> max <$> Just 3 <*> Nothing Nothing 复制代码
假设四,我们有
- 类型为 Maybe A 的奇特值
- 函数 A -> Maybe B(如果函数是 A -> B,那么很容易变成 A -> Maybe B,因为 B -> Maybe B 很容易实现)
- 想得到类型为 Maybe B 的返回值
这就是 Monad 要解决的问题。
怎么做到这一点呢?我们还是以 Maybe 为例子(因为 Maybe 实际上确实是 monad)。
先把问题简化一下:
- 类型为 A 的普通值
- 函数 A -> Maybe B
- 想得到类型为 Maybe B 的返回值
这就很简单了,直接调用函数即可。
接下来考虑如果把参数从「类型为 A 的普通值」改为「类型为 Maybe A 的奇特值」,我们需要额外做什么事情。
很显然,如果参数是 Just A,就取出 A 值调用函数;如果参数是 Nothing,就返回 Nothing。
所以实现如下:
applyMaybe :: Maybe a -> (a -> Maybe b) -> Maybe b applyMaybe Nothing f = Nothing applyMaybe (Just x) f = f x 复制代码
Haskell 把 applyMaybe 叫做 >>=
。
Monad 类型类的定义
class Monad m where return :: a -> m a -- 跟 pure 一样 (>>=) :: m a -> (a -> m b) -> m b -- 书上说先不用管下面的 >> 和 fail (>>) :: m a -> m b -> m b x >> y = x >>= \_ -> y fail :: String -> m a fail msg = error msg 复制代码
理论上一个类型成为 Monad 的实例之前,应该先成为 Applicative 的实例,就如同 class (Functor f) => Applicative f where
一样。 但是这里的 class Monad 并没有出现 (Applicative m) 是为什么呢?
书上说是因为「在Haskell设计之初,人们没有考虑到applicative函子会这么有用」。好吧,我信了。
Maybe 是 Monad 的实例
instance Monad Maybe where return x = Just x Nothing >>= f = Nothing Just x >>= f = f x fail _ = Nothing 复制代码
用一下
ghci> return "WHAT" :: Maybe String Just "WHAT" ghci> Just 9 >>= \x -> return (x*10) -- 注意 return 不是退出 Just 90 ghci> Nothing >>= \x -> return (x*10) Nothing 复制代码
Monad 有什么意义?
上文说道
假设四,我们有
- 类型为 Maybe A 的奇特值
- 函数 A -> Maybe B
- 想得到类型为 Maybe B 的返回值
这就是 Monad 要解决的问题。
那我们为什么要研究这个问题?
书上举了一个例子,我这里简述一下。
参数 Maybe A 有两个可能,一是 Just A,而是 Nothing。
我们把 Just A 看成是成功的 A,把 Nothing 看成是失败。
那么函数 A -> Maybe B 就是能够对成功的 A 进行处理的一个函数,它的返回值是成功的 B 或者失败。
如果还有一个函数 B -> Maybe C,就可以继续处理成功的 B 了。
这很像 Promise !
- 参数为
Promise<User>
- 函数为
User -> Promise<Role>
(大部分时候我们的函数是 User -> Role,但是要把 Role 变成Promise<Role>
很简单,Promise.resolve 就能做到) - 然后我们就可以把上面两个东西连起来,得到
Promsie<Role>
了!
但是注意我没有说 Promise 就是 Monad,我目前还不知道到底是不是。
对应的 Haskell 代码就像这样
return A >>= AToMaybeB >>= BToMaybeC >>= CToMaybeD 复制代码
对应的 JS 代码
PromiseUser.then(UserToRole).then(RoleToElse) 复制代码
do 语法
普通函数里有
let x=3; y="!" in show x ++ y 复制代码
如果把 x 和 y 都放到 Maybe 里,然后用 >>=
连起来,是这样
Just 3 >>= (\x -> Just "!" >>= (\y -> Just (show x ++ y ))) 复制代码
为了免去这种麻烦的写法,我们可以用 do
foo :: Maybe String foo = do x <- Just 3 y <- Just "!" Just (show x++y ) 复制代码
所以 do 只是把 monad 值串起来的语法罢了。(没错 IO 也是 monad)
do 要求里面每一行 <-
的右边都是一个 monad 值。上例中每一行都是一个 Maybe 值。
模式匹配与 fail
justH :: Maybe Char justH = do (x:xs) <- Just "hello" return x 复制代码
最终 justH 的值是 Just 'h'。 但是如果模式匹配失败了会怎么办?
wopwop :: Maybe Char wopwop = do (x:xs) <- Just "" return x 复制代码
"" 是一个空的列表,没有办法得到 x,那么就会调用 monal 的 fail,fail 的默认实现是
fail :: (Monad m) => String -> m a fail msg = error msg -- 但是 Maybe 的 fail 是这样实现的 fail _ = Nothing 复制代码
所以如果匹配失败,会得到 Nothing,以避免程序崩溃,这很巧妙。
列表是 Monad
instance Monad [] where return x = [x] xs >>= f = concat (map f xs) fail _ = [] 复制代码
使用示例:
ghci> [3,4,5] >>= \x -> [x,x] [3,-3,4,-4,5,-5] ghci> [] >>= \x -> ["bad","mad","rad"] [] ghci> [1,2,3] >>= \x -> [] [] ghci> [1,2] >>= \n -> ['a','b'] >>= \ch -> return(n,ch) [(1,'a'),(1,'b'),(2,'a'),(2,'b')] -- 用 do 改写 listOfTuples :: [(Int,Char)] listOfTuples = do n<-[1,2] ch<-['a','b'] return (n,ch) 复制代码
monad 定律
Haskell 无法检查一个类型是否满足 monad 定律,需要开发者自己确保。
- 左单位元律——
return x >>= f
的值必须和f x
一样 - 右单位元律——
m >>= return
和m
的值必须一样 - 结合律——
(m >>= f) >>= g
和 m>>= (\x -> f x >>= g)
一样
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- JavaScript权威指南笔记
- Elasticsearch权威指南学习笔记
- 《Haskell趣学指南》笔记之模块
- 《Haskell趣学指南》笔记之函数
- redis 运维和开发指南-学习笔记
- 《Haskell趣学指南》笔记之高阶函数
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Learning JavaScript
Shelley Powers / Oreilly & Associates Inc / 2006-10-17 / $29.99
As web browsers have become more capable and standards compliant, JavaScript has grown in prominence. JavaScript lets designers add sparkle and life to web pages, while more complex JavaScript has led......一起来看看 《Learning JavaScript》 这本书的介绍吧!