Skip to main content

Functor-Applicative-Monad

📅 2026-04-08 ✏️ 2026-04-08 CS

1 · Functor-Applicative-Monad#

函子 Functor: 将函数应用到盒子中的值 应用子 Applicative: 将盒子中的函数应用到盒子中的值 单子 Monad: 函数产生值在盒子中

三者都解决同一个核心问题:如何对”盒子里的值”施加变换,区别在于”函数”和”值”各自是否在盒子里:

抽象函数签名(Haskell 风格)
Functor普通函数 a -> b盒子里 F afmap :: (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 3Just 4,盒子的”形状”从未改变,只有里面的值变了。
  • <*>组合:两个盒子进,上下文需要合并。函数在一个盒子里,值在另一个盒子里,必须把两个盒子的上下文”合”成一个。合并规则是固定的(都有值才有值、列表做叉积),不会因为第一个盒子里的值而改变第二个盒子的上下文。
  • >>=链式:一个盒子进,但下一个盒子的上下文由值决定。值从盒子里取出后,喂给函数,函数根据这个值生成新的盒子(新的上下文),最后压平。上下文不再是独立合并,而是前一步的值驱动后一步的上下文,形成依赖链。

一句话:映射不动盒子,组合合并盒子,链式让值决定下一个盒子。

为什么是”逐层增强”:每一层都能用自己的原语实现上一层的操作,反之不行。

  • <*> + pure → 可实现 fmapfmap f x = pure f <*> x
  • >>= + return → 可实现 <*>fs <*> xs = fs >>= \f -> xs >>= \x -> return (f x)