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

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


当前回答

数学思维

简而言之:用于组合计算的代数结构。

返回数据:创建一个只需在monad世界中生成数据的计算。

(return data)>>=(return func):第二个参数接受第一个参数作为数据生成器,并创建连接它们的新计算。

您可以认为(>>=)和return本身不会进行任何计算。他们只是简单地组合和创建计算。

当且仅当main触发时,任何monad计算都将被计算。

其他回答

遵循您简短、简洁、实用的指示:

理解monad最简单的方法是在上下文中应用/组合函数。假设你有两个计算,它们都可以看作是两个数学函数f和g。

f取一个String并生成另一个String(取前两个字母)g获取一个String并生成另一个String(大写转换)

因此,在任何语言中,“取前两个字母并将其转换为大写”的转换都会写成g(f(“某个字符串”))。因此,在纯完美函数的世界中,合成只是:先做一件事,然后再做另一件事。

但假设我们生活在一个功能可能失败的世界中。例如:输入字符串可能有一个字符长,因此f将失败。所以在这种情况下

f获取一个String并生成一个String或Nothing。g仅在f未失败时生成字符串。否则,将不生成任何内容

所以现在,g(f(“somestring”))需要一些额外的检查:“计算f,如果它失败,那么g应该返回Nothing,否则计算g”

此思想可应用于任何参数化类型,如下所示:

让Context[Sometype]是Context中Sometype的计算。考虑功能

f: :AnyType->上下文[Sometype]g: :某些类型->上下文[AnyOtherType]

合成g(f())应该读作“compute f。在这个上下文中,做一些额外的计算,然后计算g,如果它在上下文中有意义”

在Coursera“反应式编程原理”培训中,Erik Meier将其描述为:

"Monads are return types that guide you through the happy path." -Erik Meijer

解释monad似乎就像解释控制流语句一样。想象一下,一个非程序员要求你解释它们?

你可以给他们一个涉及理论的解释——布尔逻辑、寄存器值、指针、堆栈和框架。但那太疯狂了。

你可以用语法来解释它们。基本上,C中的所有控制流语句都有大括号,您可以通过它们相对于括号的位置来区分条件和条件代码。这可能更疯狂。

或者,您也可以解释循环、if语句、例程、子例程以及可能的协例程。

Monad可以取代相当多的编程技术。语言中有一种特定的语法支持它们,还有一些关于它们的理论。

它们也是函数式程序员使用命令式代码而不承认它的一种方式,但这并不是他们唯一的用途。

monad是一种将共享共同上下文的计算组合在一起的方法。这就像建立一个管道网络。当构建网络时,没有数据流过它。但是当我用“bind”和“return”将所有位拼接在一起后,我调用类似runMyMonad monad数据的东西,数据流过管道。

这个答案从一个激励性的例子开始,通过这个例子,得出一个单子的例子,并正式定义了“单子”。

考虑伪代码中的这三个函数:

f(<x, messages>) := <x, messages "called f. ">
g(<x, messages>) := <x, messages "called g. ">
wrap(x)          := <x, "">

f采用<x,messages>形式的有序对,并返回一个有序对。它保持第一项不变,并在第二项后面附加“called f.”。与g相同。

您可以组合这些函数并获得原始值,以及显示函数调用顺序的字符串:

  f(g(wrap(x)))
= f(g(<x, "">))
= f(<x, "called g. ">)
= <x, "called g. called f. ">

您不喜欢f和g负责将自己的日志消息附加到先前的日志信息。(为了论证起见,想象一下,f和g必须对这对中的第二项执行复杂的逻辑,而不是附加字符串。在两个或多个不同的函数中重复这种复杂的逻辑会很痛苦。)

您更喜欢编写更简单的函数:

f(x)    := <x, "called f. ">
g(x)    := <x, "called g. ">
wrap(x) := <x, "">

但看看当你编写它们时会发生什么:

  f(g(wrap(x)))
= f(g(<x, "">))
= f(<<x, "">, "called g. ">)
= <<<x, "">, "called g. ">, "called f. ">

问题是,将一对传递到函数中并不能得到所需的结果。但如果你可以将一对输入到函数中呢:

  feed(f, feed(g, wrap(x)))
= feed(f, feed(g, <x, "">))
= feed(f, <x, "called g. ">)
= <x, "called g. called f. ">

将feed(f,m)读为“feed m into f”。要将一对<x,messages>输入函数f,需要将x传递给f,从f中获取<y,messages〕,并返回<y,message message>。

feed(f, <x, messages>) := let <y, message> = f(x)
                          in  <y, messages message>

请注意,当您对函数执行三项操作时会发生什么:

首先:如果包装一个值,然后将结果对送入函数:

  feed(f, wrap(x))
= feed(f, <x, "">)
= let <y, message> = f(x)
  in  <y, "" message>
= let <y, message> = <x, "called f. ">
  in  <y, "" message>
= <x, "" "called f. ">
= <x, "called f. ">
= f(x)

这与将值传递给函数相同。

第二:如果你把一对放进包装里:

  feed(wrap, <x, messages>)
= let <y, message> = wrap(x)
  in  <y, messages message>
= let <y, message> = <x, "">
  in  <y, messages message>
= <x, messages "">
= <x, messages>

这不会改变这对。

第三:如果定义了一个函数,该函数将x和g(x)输入f:

h(x) := feed(f, g(x))

并向其中输入一对:

  feed(h, <x, messages>)
= let <y, message> = h(x)
  in  <y, messages message>
= let <y, message> = feed(f, g(x))
  in  <y, messages message>
= let <y, message> = feed(f, <x, "called g. ">)
  in  <y, messages message>
= let <y, message> = let <z, msg> = f(x)
                     in  <z, "called g. " msg>
  in <y, messages message>
= let <y, message> = let <z, msg> = <x, "called f. ">
                     in  <z, "called g. " msg>
  in <y, messages message>
= let <y, message> = <x, "called g. " "called f. ">
  in <y, messages message>
= <x, messages "called g. " "called f. ">
= feed(f, <x, messages "called g. ">)
= feed(f, feed(g, <x, messages>))

这与将对输入g和将所得对输入f相同。

你有大部分的单子。现在您只需要了解程序中的数据类型。

<x,“称为f”>是什么类型的值?这取决于x是什么类型的值。如果x是t类型的,那么你的对就是“t和字符串对”类型的值了。称之为M型。

M是一个类型构造器:M本身并不表示一个类型,但一旦你用一个类型填空,M _就表示一个。M int是一对int和一个字符串。M字符串是一对字符串和一个字符串。等

恭喜你,你已经创建了monad!

形式上,你的monad是元组<M,feed,wrap>。

monad是一个元组<M,feed,wrap>,其中:

M是类型构造函数。feed接受一个(函数接受一个t并返回一个M u)和一个M t并返回M u。wrap接受一个v并返回一个M v。

t、 u和v是可以相同也可以不同的任意三种类型。单子满足您为特定单子证明的三个财产:

将包裹的t送入函数与将未包裹的t传入函数相同。形式上:饲料(f,包装(x))=f(x)将M t喂入包装物对M t没有任何影响。形式上:进给(包裹,m)=m将一个M t(称为M)输入一个函数将t传递到g从g得到一个M u(称为n)将n输入f与m进g从g得到n将n输入f形式上:饲料(h,m)=饲料(f,饲料(g,m)),其中h(x):=饲料(f,g(x))

通常,feed称为bind(在Haskell中为AKA>>=),wrap称为return。