Functor-Applicative-Monad
Outlinks (0)
No outlinks found
Backlinks (1)
Backlinks (1)
1 · Functor-Applicative-Monad#
函子 Functor: 将函数应用到盒子中的值
应用子 Applicative: 将盒子中的函数应用到盒子中的值
单子 Monad: 函数产生值在盒子中
三者都解决同一个核心问题:如何对”盒子里的值”施加变换,区别在于”函数”和”值”各自是否在盒子里:
| 抽象 | 函数 | 值 | 签名(Haskell 风格) |
|---|---|---|---|
| Functor | 普通函数 a -> b | 盒子里 F a | fmap :: (a -> b) -> F a -> F b |
| Applicative | 盒子里 F (a -> b) | 盒子里 F a | <*> :: F (a -> b) -> F a -> F b |
| Monad | 普通函数,但返回盒子 a -> F b | 盒子里 F a | >>= :: F a -> (a -> F b) -> F b |
逐个解读签名:
fmap :: (a -> b) -> F a -> F b
接收一个普通函数 a -> b 和一个盒子 F a,把函数”探进”盒子里作用于值,结果仍包在盒子里。盒子本身不变,只变里面的值。
fmap (+1) (Just 3) -- Just 4
fmap (+1) Nothing -- Nothing(盒子是空的,无事发生)
fmap (*2) [1,2,3] -- [2,4,6](List 也是一种盒子)
<*> :: F (a -> b) -> F a -> F b
函数也在盒子里。<*> 把盒子里的函数取出来,应用到另一个盒子里的值上。典型场景:多个独立的盒子值要组合在一起。
Just (+1) <*> Just 3 -- Just 4
Nothing <*> Just 3 -- Nothing(函数那边是空的,短路)
Just (+) <*> Just 3 <*> Just 5 -- Just 8(柯里化:先 Just (+3),再应用到 5)
>>= :: F a -> (a -> F b) -> F b(读作 “bind”)
接收一个盒子 F a 和一个函数 a -> F b(函数自己会产生新盒子)。先从盒子里取出 a,喂给函数,函数返回 F b。若用 fmap 做同样的事,结果会是 F (F b)(盒子套盒子),而 >>= 会自动压平成 F b。
Just 3 >>= \x -> if x > 2 then Just (x * 10) else Nothing -- Just 30
Nothing >>= \x -> Just (x * 10) -- Nothing(上游为空,直接短路)
[1,2,3] >>= \x -> [x, x*100] -- [1,100,2,200,3,300](压平)
总结——逐层增强的能力链(从盒子/上下文的角度理解):
Functor ⊂ Applicative ⊂ Monad
fmap→ 映射:一个盒子进,一个盒子出,上下文原封不动。Just 3→Just 4,盒子的”形状”从未改变,只有里面的值变了。<*>→ 组合:两个盒子进,上下文需要合并。函数在一个盒子里,值在另一个盒子里,必须把两个盒子的上下文”合”成一个。合并规则是固定的(都有值才有值、列表做叉积),不会因为第一个盒子里的值而改变第二个盒子的上下文。>>=→ 链式:一个盒子进,但下一个盒子的上下文由值决定。值从盒子里取出后,喂给函数,函数根据这个值生成新的盒子(新的上下文),最后压平。上下文不再是独立合并,而是前一步的值驱动后一步的上下文,形成依赖链。
一句话:映射不动盒子,组合合并盒子,链式让值决定下一个盒子。
为什么是”逐层增强”:每一层都能用自己的原语实现上一层的操作,反之不行。
- 有
<*>+pure→ 可实现fmap:fmap f x = pure f <*> x - 有
>>=+return→ 可实现<*>:fs <*> xs = fs >>= \f -> xs >>= \x -> return (f x)