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

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

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),然后根据你就可以自己决定怎么组合它们……

Monad 的简单类比通常是不全面甚至错误的,尤其是认为 Monad 和 IO、顺序执行、副作用、命令式等必然相关。这里我引用一下

What I Wish I Knew When Learning Haskell 2.2 ( Stephen Diehl )

Monadic Myths

The following are all false:

  • Monads are impure.
  • Monads are about effects.
  • Monads are about state.
  • Monads are about imperative sequencing.
  • Monads are about IO.
  • Monads are dependent on laziness.
  • Monads are a "back-door" in the language to perform side-effects.
  • Monads are an embedded imperative language inside Haskell.
  • Monads require knowing abstract mathematics.