51

每次有人承诺要“解释单子”时,我的兴趣都会被激起,但当所谓的“解释”是一长串例子被一些随口说“深奥背后的“数学理论”想法”是“在这一点上解释得太复杂了”。

现在我要求相反。我对范畴论有扎实的掌握,我不害怕图表追逐、米田引理或派生函子(实际上是范畴意义上的单子和附属词)。

有人能给我一个关于函数式编程中什么是 monad的清晰简洁的定义吗?例子越少越好:有时一个清晰的概念会说一百多个胆小的例子。尽管我并不挑剔,但 Haskell 作为一种演示语言会做得很好。

4

8 回答 8

31

这个问题有一些很好的答案:Monads as adjunctions

更重要的是,Derek Elkins 在 TMR #13 中的“用范畴理论计算单子”文章应该有你正在寻找的那种结构:http ://www.haskell.org/wikiupload/8/85/TMR-问题13.pdf

最后,也许这真的是最接近您正在寻找的内容,您可以直接访问源代码并查看 Moggi 在 1988-91 期间关于该主题的开创性论文:http ://www.disi.unige.it/人/Moggie/publications.html

特别参见“计算和单子的概念”。


我自己的我肯定太浓缩/不精确:

Hask从对象是 Haskell 类型、态射是函数的范畴开始。函数也是 中的对象Hask,产品也是。Hask笛卡尔封闭也是如此。现在引入一个箭头,将每个对象映射HaskMHask其中的对象的子集Hask。单元!接下来介绍一个将每个箭头映射到 上Hask的箭头的箭头MHask。这给了我们地图,并制作MHask了一个协变的内函子。现在引入一个箭头MHask,将生成的每个对象MHask(通过单元)映射到MHask生成它的对象。加入!从那开始,MHask是一个单子(更准确地说是一个单形内函子)。

我确信上述内容不足是有原因的,这就是为什么如果您正在寻找形式主义,我真的会指导您,特别是 Moggi 论文。

于 2011-11-22T03:59:40.753 回答
18

作为对卡尔回答的补充,Haskell 中的 Monad(理论上)是这样的:

class Monad m where
  join :: m (m a) -> m a
  return :: a -> m a
  fmap :: (a -> b) -> m a -> m b

请注意,“绑定” ( >>=) 可以定义为

x >>= f = join (fmap f x)

根据Haskell Wiki

C类中的 monad是三元组 ( F : C → C, η : IdF , μ : FFF )

...有一些公理。对于 Haskell,fmapreturn和分别joinF、 η 和 μ 对齐。(fmap在 Haskell 中定义了一个 Functor)。如果我没记错的话,Scala 分别调用这些map,purejoin。(Scala 调用绑定“flatMap”)

于 2011-11-22T05:14:46.513 回答
12

好的,使用 Haskell 术语和示例...

在函数式编程中,monad 是一种数据类型的组合模式* -> *

class Monad (m :: * -> *) where
    return :: a -> m a
    (>>=)  :: m a -> (a -> m b) -> m b

(这个类比 Haskell 中的更多,但这些是重要的部分。)

如果一个数据类型可以实现该接口同时满足实现中的三个条件,那么它就是一个 monad。这些是“单子定律”,我将把它留给那些冗长的解释以获得完整的解释。我将这些定律总结为“(>>= return)是一个恒等函数,并且(>>=)是关联的”。真的也不过如此,就算能表达的更准确。

这就是一个单子。如果您可以在保留这些行为属性的同时实现该接口,那么您就有了一个 monad。

该解释可能比您预期的要短。那是因为 monad 接口确实非常抽象。令人难以置信的抽象水平是为什么这么多不同的事物可以建模为 monad 的部分原因。

不太明显的是,与接口一样抽象,它允许对任何控制流模式进行通用建模,而不管实际的 monad 实现如何。这就是为什么Control.MonadGHCbase库中的包具有 , 等组合when符的forever原因。这就是为什么显式抽象任何 monad 实现的能力非常强大的原因,尤其是在类型系统的支持下。

于 2011-11-22T04:03:59.173 回答
6

您应该阅读 Eugenio Moggi 的论文“计算和单子的概念”,该论文解释了当时提议的单子在构建有效语言的指称语义中的作用。

还有一个相关的问题:

学习 Haskell 等纯函数式语言背后的理论的参考资料?

由于您不想挥手,因此您必须阅读科学论文,而不是论坛答案或教程。

于 2011-11-22T12:03:02.583 回答
5

单子是内函子类别中的幺半群,有什么问题?.

撇开幽默不谈,我个人认为,在 Haskell 和函数式编程中使用 monad 时,最好从monads-as-an-interface的角度(如 Carl 和 Dan 的回答)而不是monads-as-从类别理论的角度来看。我必须承认,当我不得不在实际项目中使用来自另一种语言的monad 库时,我才真正内化了整个 monad 的东西。

您提到您不喜欢所有“大量示例”教程。有没有人向你指出尴尬的小队文件?它着重于 IO monad,但引言给出了一个很好的技术和历史解释,解释了为什么monad 概念首先被 Haskell 所接受。

于 2011-11-22T14:12:05.240 回答
4

由于您在类别理论意义上理解 monad,我将您的问题解释为关于函数式编程中 monad 的表示因此,我的回答避免了对 monad是什么的任何解释,或者对它的含义或用途的任何直觉。

答案:在 Haskell 中,monad 以某种类别的内部语言呈现为 Kleisli 三元组的(内部化)映射。

解释: 很难准确描述“Hask类别”的属性,这些属性在很大程度上与理解 Haskell 对 monad 的表示无关。相反,对于本次讨论,将 Haskell 理解为某些C类的内部语言会更有用。Haskell 函数在C中定义态射,而 Haskell 类型是C中的对象,但做出这些定义的特定类别并不重要。

参数数据类型,例如data F a = ...,是对象映射,例如F : |C | -> |C|

Haskell 中对 monad 的通常描述是Kleisli 三元组(或Kleisli 扩展)形式:

class Monad m where
    return :: a -> m a
    (>>=) :: m a -> (a -> m b) -> m b

在哪里:

  • m对象映射 m :|C | -> |C|
  • return是对对象的单位操作
  • >>=(Haskellers 读作“bind”)是态射的扩展操作,但前两个参数交换了(参见扩展的通常签名(-)* : (a -> m b) -> m a -> m b

(这些映射本身被化为C中的态射,这在C Cm :|是可能的)。| -> ||

因此, Haskell 的do表示法(如果您遇到过这种情况)是 Kleisli 类别的内部语言。

于 2012-06-07T13:22:39.213 回答
4

我真的不知道我在说什么,但这是我的看法:

Monad 用于表示计算。您可以将一个普通的过程程序(基本上是一个语句列表)想象成一堆组合计算。Monad 是这个概念的概括,允许您定义语句的组合方式。每个计算都有一个值(它可能只是());monad 只是确定通过一系列计算串起来的值的行为方式。

Do 表示法确实使这一点变得清晰:它基本上是一种特殊的基于语句的语言,可让您定义语句之间发生的事情。就好像你可以定义如何“;” 使用类 C 语言工作。

从这个角度来看,到目前为止我使用的所有 monad 都是有意义State的:不影响值,但会更新第二个值,该值在后台从计算传递到计算;Maybe如果遇到 a ,则将值短路NothingList允许您传递可变数量的值;IO让您以安全的方式传递不纯的值。我使用过的更专业的 monadGen和 Parsec 解析器也很相似。

希望这是一个明确的解释,并非完全偏离基础。

于 2011-11-22T03:42:36.633 回答
2

Haskell wikibook 页面有一个很好的基本解释。

于 2011-11-22T08:16:48.480 回答