怎样用简单的语言解释 monad?

关注者
1,119
被浏览
167,716

39 个回答

使用 join 诠释的话,Monad 会有一个非常不同的理解:Monad 是可以增(return/unit,\mathrm{Id}\rightarrow T)减(join,T^2\rightarrow T)层数的「箱子」。而 unit 和 join 因为满足某些定律所以形成了一个幺半群(对,这就是那老梗)。所以,Monad 代表的是层次,而不是顺序。(回想下 CPS,是不是用层次表示顺序的?)

Haskell 的 bind 可以看作 fmap 和 join 的复合


首先解释 Endofunctor,Endofunctor 是(不加限制的)箱子,它把类型(一个类型范畴里的对象)T 装进去变成 F(T),同时还能保证所有函数(态射)A->B,都有一个带箱子的版本 F(A)->F(B)。

而 Monad 呢?就是除了前面的两个箱子,我们还能定义出把任意类型的数值装进箱子的 unit:T->F(T),以及把两层箱子只留一层的 join:F(F(T))->F(T)。

Endofunctor 们可以组成一个新的范畴,其中有个很简单的元素 Id,它代表的「箱子」就是「没箱子」,恒等变换。由于恒等变换 Id 存在,所以我们可以尝试着用「二阶」的手段去掉 unit 和 join 签名里面的 T,只关心 F 的部分,于是我们有了:

  • Unit : Id -> F
  • Join : F × F -> F

这个形式和幺半群很相似,所以我们说:A Monad is a monoid object in a category of endofunctors。

--

  • 对于数组,箱子就是数组本身,join 定义成 concat,把两层数组合并成一层
  • 对于 Maybe,箱子是 Nothing/Just 包围,join 定义成把整套结构排扁
  • 对于「副作用」,箱子是对某种状态的修改(用函数的形式表示),join 定义成副作用的复合
  • 对于 Cont,箱子是形如\lambda k.\,k\,x的一层封装(接受一个 continuation,把「里面的东西」丢给他;数学上是一个双对偶空间),join 就定义为\mathrm{join}\ f\ k=f\,(\lambda f'.\,f'\,k) =(\lambda k_2.\ k_2\ (\lambda k_1.\ k_1\ x))(\lambda f'.\,f'\,k)=(\lambda f'.\,f'\,k)(\lambda k_1.\ k_1\ x)=(\lambda k_1.\ k_1\ x)\,k=k\ x
  • Free monad 就是构造间隔的红绿两层盒子,在 join 那一步抽掉绿色的盒子(Free F),只留下红色的(F),然后根据你就可以自己决定怎么组合它们……

写个答案给知道数学的 Haskell 初学者看:

如果 F 是 monad, 那任意一个 monadic map X\to FY 可以升级成 FX\to FY , 记住下面这个交换图表就行了( i 是 return, 是一个自然变换 \text{id}\to F , j 是 join, 是一个自然变换 FF \to F , 对角线上 FX\to FY 其实是 (>>=f).),这个图表上下两个三角形都是交换的。

\begin{CD} X @> i >> FX\\ @V f V V\swarrow @VV Ff V\\ FY @<<j< FFY \end{CD}

f = i 的时候对角线上是 \text{id} .

记住这个图表以后怎么 compose monadic map 也是显然的——

\begin{CD} @. @. X @> i >> FX\\ @. @. @V f V V\swarrow @VV Ff V\\ @.Y @> i >> FY @<<j< FFY\\ @. @V g V V\swarrow @VV Fg V\\ @. FZ @<<j< FFZ \end{CD}

g >=> f 就是上图中的 X\to FY\to FZ .

从这个图表看,>=> 的结合律是显然的(想想 [**] 那里就可以)

  h >=> (g >=> f) 
= h >=> ((>>= g)·f)
= (>>= h)·((>>= g)·f)
= ((>>= h)·(>>= g))·f
= (>>= (h >=> g))·f [**]
= (h >=> g) >=> f

最初那个交换图表甚至可以用作 monad 的定义。