在最近简要回顾了Haskell之后,对于monad本质上是什么,有什么简单、简洁、实用的解释?

我发现,我遇到的大多数解释都很难理解,而且缺乏实际细节。


当前回答

在Scala的上下文中,您会发现以下是最简单的定义。基本上,flatMap(或bind)是“关联”的,并且存在一个标识。

trait M[+A] {
  def flatMap[B](f: A => M[B]): M[B] // AKA bind

  // Pseudo Meta Code
  def isValidMonad: Boolean = {
    // for every parameter the following holds
    def isAssociativeOn[X, Y, Z](x: M[X], f: X => M[Y], g: Y => M[Z]): Boolean =
      x.flatMap(f).flatMap(g) == x.flatMap(f(_).flatMap(g))

    // for every parameter X and x, there exists an id
    // such that the following holds
    def isAnIdentity[X](x: M[X], id: X => M[X]): Boolean =
      x.flatMap(id) == x
  }
}

E.g.

// These could be any functions
val f: Int => Option[String] = number => if (number == 7) Some("hello") else None
val g: String => Option[Double] = string => Some(3.14)

// Observe these are identical. Since Option is a Monad 
// they will always be identical no matter what the functions are
scala> Some(7).flatMap(f).flatMap(g)
res211: Option[Double] = Some(3.14)

scala> Some(7).flatMap(f(_).flatMap(g))
res212: Option[Double] = Some(3.14)


// As Option is a Monad, there exists an identity:
val id: Int => Option[Int] = x => Some(x)

// Observe these are identical
scala> Some(7).flatMap(id)
res213: Option[Int] = Some(7)

scala> Some(7)
res214: Some[Int] = Some(7)

注:严格地说,函数编程中的Monad的定义与范畴理论中的Monard的定义不同,后者是按映射和展平的顺序定义的。尽管它们在某些映射下是等价的。这个演示非常好:http://www.slideshare.net/samthemonad/monad-presentation-scala-as-a-category

其他回答

公主对F#计算表达式的解释帮助了我,尽管我仍然不能说我真的理解了。

编辑:这个系列-用javascript解释monad-对我来说是一个“打破平衡”的系列。

http://blog.jcoglan.com/2011/03/05/translation-from-haskell-to-javascript-of-selected-portions-of-the-best-introduction-to-monads-ive-ever-read/http://blog.jcoglan.com/2011/03/06/monad-syntax-for-javascript/http://blog.jcoglan.com/2011/03/11/promises-are-the-monad-of-asynchronous-programming/

我认为理解单子是一件让你毛骨悚然的事。从这个意义上说,尽可能多地阅读“教程”是一个好主意,但通常奇怪的东西(不熟悉的语言或语法)会让你的大脑无法专注于基本内容。

有些事情我很难理解:

基于规则的解释对我来说从未奏效,因为大多数实际示例实际上需要的不仅仅是返回/绑定。此外,称之为规则也无济于事。这更像是“有些东西有共同点,我们把它们称为‘单子’,把共同点称为‘规则’”。Return(a->M<a>)和Bind(M<a>->(a->M<b>)->M<b>)很好,但我永远无法理解Bind如何从M<a>中提取a,以便将其传递给a->M<b>。我不认为我在任何地方读过(也许这对其他人来说都很明显),Return(M<a>->a)的反面必须存在于monad内部,它只是不需要暴露。

如果你要求对如此抽象的东西做出简洁、实用的解释,那么你只能希望得到一个抽象的答案:

a -> b

是表示从as到bs的计算的一种方式。您可以将计算链接起来,也可以将它们组合在一起:

(b -> c) -> (a -> b) -> (a -> c)

更复杂的计算需要更复杂的类型,例如:

a -> f b

是从as到bs到fs的计算类型。您还可以编写它们:

(b -> f c) -> (a -> f b) -> (a -> f c)

事实证明,这种模式无处不在,并且与上面的第一个组合具有相同的财产(结合性、右-和左-同一性)。

人们必须给这个模式起一个名字,但如果知道第一个组合被正式描述为半群体,这会有帮助吗?

“单子和圆括号一样有趣和重要”(奥列格·基斯廖夫)

但是,你本可以发明蒙纳斯!

sigfpe说:但所有这些都将单子介绍为需要解释的深奥的东西。但我想说的是,它们一点都不深奥。事实上,面对函数式编程中的各种问题,你会不可避免地被引向某些解决方案,所有这些都是单子的例子。事实上,如果你还没有发明,我希望你现在就发明它们。这是注意到所有这些解决方案实际上都是变相的相同解决方案的一小步。读完这篇文章后,你可能会更好地理解单子上的其他文档,因为你会发现你所看到的一切都是你已经发明的。monads试图解决的许多问题都与副作用有关。因此,我们将从它们开始。(请注意,monad让您做的不仅仅是处理副作用,特别是许多类型的容器对象都可以被视为monad。monad的一些介绍发现,很难协调monad的这两种不同用法,并且只关注其中一种。)在命令式编程语言(如C++)中,函数的行为与数学函数完全不同。例如,假设我们有一个C++函数,它接受一个浮点参数并返回一个浮点结果。从表面上看,它可能有点像一个将实数映射到实数的数学函数,但C++函数可以做的不仅仅是返回一个依赖于其参数的数字。它可以读取和写入全局变量的值,也可以将输出写入屏幕并接收用户的输入。然而,在纯函数语言中,函数只能读取在其参数中提供给它的内容,而它对世界产生影响的唯一方式是通过它返回的值。

实际上,monad是函数组合运算符的一种自定义实现,它考虑了副作用以及不兼容的输入和返回值(用于链接)。

http://mikehadlow.blogspot.com/2011/02/monads-in-c-8-video-of-my-ddd9-monad.html

这是你要找的视频。

用C#演示组合和对齐类型的问题,然后用C#正确实现它们。最后,他展示了F#和Haskell中相同的C#代码的外观。