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

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


当前回答

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

其他回答

(另请参见“什么是monad?”中的答案)

蒙纳斯的一个很好的动机是西格菲(丹·皮波尼)的《你本可以发明蒙纳斯!(也许你已经有了)。还有很多其他monad教程,其中许多都试图使用各种类比以“简单的术语”来解释monad:这就是monad教程谬论;避开它们。

正如MacIver博士在《告诉我们为什么你的语言很糟糕》中所说:所以,我讨厌Haskell的事情:让我们从显而易见的开始。Monad教程。不,不是单子。特别是教程。他们没完没了,夸夸其谈,亲爱的上帝,他们太乏味了。此外,我从未见过任何令人信服的证据表明它们确实有帮助。阅读类定义,编写一些代码,忘掉这个可怕的名字。

你说你懂“也许莫纳德”吗?很好,你在路上了。只要开始使用其他monad,迟早你会了解monad的一般含义。

(如果你以数学为导向,你可能想忽略几十个教程,学习定义,或遵循类别理论的讲座:)定义的主要部分是Monad M包含一个“类型构造器”,为每个现有类型“T”定义一个新类型“M T”,以及在“常规”类型和“M”类型之间来回移动的一些方式。]

同样,令人惊讶的是,对monad最好的介绍之一实际上是介绍monad的早期学术论文之一,Philip Wadler的Monad for functional programming。它实际上有一些实用的、非平凡的激励性例子,与许多人工教程不同。

另一种尝试是解释monad,只使用Python列表和map函数。我完全接受这不是一个完整的解释,但我希望它能触及核心概念。

我从Monads上的funfunfunction视频和Learn You A Haskell章节“为了几个Monads更多”中得到了这一点的基础。我强烈推荐观看funfunfunction视频。

最简单的是,Monad是具有map和flatMap函数(在Haskell中绑定)的对象。有一些额外的必需财产,但这些是核心属性。

flatMap“展平”map的输出,对于列表,这只是连接列表的值,例如。

concat([[1], [4], [9]]) = [1, 4, 9]

因此,在Python中,我们基本上可以通过以下两个函数实现Monad:

def flatMap(func, lst):
    return concat(map(func, lst))

def concat(lst):
    return sum(lst, [])

func是任何接受值并返回列表的函数。

lambda x: [x*x]

解释

为了清楚起见,我通过一个简单的函数在Python中创建了concat函数,该函数将列表相加,即[]+[1]+[4]+[9]=[1,4,9](Haskell有一个原生的concat方法)。

我假设你知道地图功能是什么,例如:

>>> list(map(lambda x: [x*x], [1,2,3]))
[[1], [4], [9]]

展平是Monad的关键概念,对于每个作为Monad的对象,这种展平允许您获得Monad中包裹的值。

现在我们可以呼叫:

>>> flatMap(lambda x: [x*x], [1,2,3])
[1, 4, 9]

这个lambda取一个值x并将其放入一个列表中。monad适用于从值到monad类型的任何函数,所以在本例中是列表。

这是你的monad定义。

我认为为什么它们有用的问题已经在其他问题中得到了回答。

更多说明

其他不是列表的例子有JavaScript Promise,它有then方法,JavaScript Streams有flatMap方法。

因此Promise和Streams使用了一个稍微不同的函数,它将Stream或Promise展平,并从内部返回值。

Haskell列表monad具有以下定义:

instance Monad [] where  
    return x = [x]  
    xs >>= f = concat (map f xs)  
    fail _ = [] 

即有三个函数return(不要与大多数其他语言中的return混淆)、>>=(flatMap)和fail。

希望您能看到以下两者之间的相似之处:

xs >>= f = concat (map f xs)

and:

def flatMap(f, xs):
    return concat(map(f, xs))

在了解这些信息时,对我帮助最大的两件事是:

第8章,“函数解析器”,摘自Graham Hutton的《Haskell编程》一书。实际上,这根本没有提到monad,但如果您能够通读第章并真正理解其中的所有内容,特别是如何评估一系列绑定操作,您将了解monad的内部结构。预计这需要多次尝试。

关于修道院的教程。这提供了几个很好的例子来说明它们的用途,我不得不说,我在Appendex中的类比是为我工作的。

Monoid似乎可以确保在Monoid和受支持的类型上定义的所有操作始终返回Monoid内部的受支持类型。任何数字+任何数字=一个数字,没有错误。

而除法接受两个分数,并返回一个分数,该分数在haskell somewhy中将除以零定义为无穷大(恰好是分数somewhy)。。。

在任何情况下,Monads似乎只是一种确保您的操作链以可预测的方式运行的方法,而一个声称为Num->Num的函数,由另一个用x调用的Num->Num的函数组成,并不意味着发射导弹。

另一方面,如果我们有一个功能可以发射导弹,我们可以将它与其他功能组合起来,也可以发射导弹。

在Haskell中,main的类型是IO()或IO[()],这种区分很奇怪,我不会讨论它,但我认为会发生以下情况:

如果我有main,我希望它做一系列动作,我运行程序的原因是产生一个效果——通常是通过IO。因此,我可以将IO操作串联在一起,以便——做IO,而不是其他。

如果我尝试做一些不“返回IO”的事情,程序会抱怨链不流动,或者基本上“这与我们正在尝试做的事情有什么关系——IO动作”,这似乎迫使程序员保持思路,不偏离并思考发射导弹,同时创建排序算法——不流动。

基本上,Monads似乎是编译器的一个提示,“嘿,你知道这个函数在这里返回一个数字,它实际上并不总是有效的,它有时会产生一个number,有时什么都没有,请记住这一点”。知道了这一点,如果你试图断言一个单元动作,单元动作可能会作为一个编译时异常,说“嘿,这实际上不是一个数字,这可能是一个数字。但你不能假设这一点。做一些事情以确保流是可接受的。”这在一定程度上防止了不可预测的程序行为。

似乎monad不是关于纯粹性,也不是关于控制,而是关于维护一个类别的身份,在这个类别上,所有行为都是可预测和定义的,或者不编译。当你被要求做某事时,你不能什么都不做,如果你被要求什么都不干(可见),你也不能做。

我能想到的Monads的最大原因是——看看程序/OOP代码,你会发现你不知道程序从哪里开始,也不知道程序的结束,你看到的只是大量的跳跃和大量的数学、魔法和导弹。您将无法维护它,如果可以的话,您将花费大量的时间来思考整个程序,然后才能理解其中的任何部分,因为在这种情况下,模块化是基于代码的相互依赖的“部分”,其中代码被优化为尽可能相关,以保证效率/相互关系。单子是非常具体的,并且通过定义得到了很好的定义,并确保程序流程可以进行分析,并隔离难以分析的部分——因为它们本身就是单子。monad似乎是一个“可理解的单元,它在完全理解时是可预测的”——如果你理解“可能”monad,那么它除了“可能”之外就没有可能做任何事情,这看起来微不足道,但在大多数非monad代码中,一个简单的函数“helloworld”可以发射导弹,什么都不做,或者摧毁宇宙,甚至扭曲时间——我们不知道也不能保证它是什么样子。一个单子保证它就是什么样子。这是非常强大的。

“现实世界”中的所有事物似乎都是单子,因为它受到防止混淆的明确可观察规律的约束。这并不意味着我们必须模仿这个对象的所有操作来创建类,相反,我们可以简单地说“一个正方形就是一个正方形”,只不过是一个正方形,甚至不是矩形或圆形,和“一个正方形的面积是它现有维度的长度乘以它自身的面积。无论你有什么正方形,如果它是2D空间中的正方形,它的面积绝对不能是任何东西,只有它的长度平方,这几乎是微不足道的。这是非常强大的,因为我们不需要断言我们的世界是这样的,我们只需要使用现实的含义来预测它。”防止我们的节目偏离轨道。

我几乎可以肯定是错的,但我认为这可以帮助一些人,所以希望它能帮助一些人。

monad实际上是“类型运算符”的一种形式。它将做三件事。首先,它会将一种类型的值“包装”(或以其他方式转换)为另一种类型(通常称为“一元类型”)。第二,它将使底层类型上的所有操作(或函数)在monadic类型上可用。最后,它将为将自身与另一个monad组合以生成复合monad提供支持。

“可能monad”本质上等同于Visual Basic/C#中的“可为null的类型”。它接受不可为null的类型“T”并将其转换为“可为null<T>”,然后定义所有二进制运算符在可为null><T>上的含义。

副作用也有类似的表现。创建了一个结构,该结构包含函数返回值旁边的副作用描述。当值在函数之间传递时,“提升”操作会复制副作用。

它们被称为“monad”,而不是更容易理解的“类型运算符”的名称,原因如下:

Monad对他们的行为有限制(详见定义)。这些限制,加上涉及三个运算,符合范畴理论中一个叫做monad的结构,这是一个模糊的数学分支。它们是由“纯”函数语言的支持者设计的纯函数语言的支持者,如模糊的数学分支由于数学晦涩难懂,而且monad与特定的编程风格相关,人们倾向于使用monad这个词作为一种秘密握手。正因为如此,没有人费心去投资一个更好的名字。