内容简介: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趣学指南》笔记之高阶函数
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Kotlin实战
【美】Dmitry Jemerov(德米特里·詹莫瑞福)、【美】 Svetlana Isakova(斯维特拉娜·伊凡诺沃) / 覃宇、罗丽、李思阳、蒋扬海 / 电子工业出版社 / 2017-8 / 89.00
《Kotlin 实战》将从语言的基本特性开始,逐渐覆盖其更多的高级特性,尤其注重讲解如何将 Koltin 集成到已有 Java 工程实践及其背后的原理。本书分为两个部分。第一部分讲解如何开始使用 Kotlin 现有的库和API,包括基本语法、扩展函数和扩展属性、数据类和伴生对象、lambda 表达式,以及数据类型系统(着重讲解了可空性和集合的概念)。第二部分教你如何使用 Kotlin 构建自己的 ......一起来看看 《Kotlin实战》 这本书的介绍吧!