在最近简要回顾了Haskell之后,对于monad本质上是什么,有什么简单、简洁、实用的解释?
我发现,我遇到的大多数解释都很难理解,而且缺乏实际细节。
在最近简要回顾了Haskell之后,对于monad本质上是什么,有什么简单、简洁、实用的解释?
我发现,我遇到的大多数解释都很难理解,而且缺乏实际细节。
当前回答
解释
当用C#/Java术语解释时,这很简单:
monad是一个接受参数并返回特殊类型的函数。这个monad返回的特殊类型也称为monad。(monad是#1和#2的组合)有一些语法糖可以使调用此函数和类型转换更容易。
实例
monad有助于使函数式程序员的生活更轻松。典型示例:Maye monad接受两个参数,一个值和一个函数。如果传递的值为null,则返回null。否则它将计算函数。如果我们需要一个特殊的返回类型,我们也可以调用这个返回类型Maybe。一个非常粗糙的实现如下所示:
object Maybe(object value, Func<object,object> function)
{
if(value==null)
return null;
return function(value);
}
这在C#中是非常无用的,因为这种语言缺乏使monad有用所需的语法糖。但是monad允许您用函数式编程语言编写更简洁的代码。
通常程序员在链中调用monad,如下所示:
var x = Maybe(x, x2 => Maybe(y, y2 => Add(x2, y2)));
在本例中,只有当x和y都为非null时,才会调用Add方法,否则将返回null。
答复
回答最初的问题: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”有点像说“什么是数字?”我们总是使用数字。但想象一下,你遇到了一个对数字一无所知的人。你怎么解释数字是什么?你怎么开始描述为什么这可能有用?
什么是单子?简单的回答是:这是一种将操作链接在一起的特定方式。
本质上,您正在编写执行步骤,并将它们与“绑定函数”链接在一起。(在Haskell中,它名为>>=。)您可以自己编写对绑定运算符的调用,也可以使用语法糖,使编译器为您插入这些函数调用。但无论哪种方式,每个步骤都由对该绑定函数的调用分隔。
因此绑定函数就像分号;它将流程中的步骤分开。bind函数的任务是获取上一步的输出,并将其输入下一步。
听起来不太难,对吧?但单子不止一种。为什么?怎样
好吧,bind函数可以从一个步骤中获取结果,并将其传递给下一个步骤。但如果这就是单子的全部。。。这实际上不是很有用。理解这一点很重要:每个有用的monad除了做monad之外,还做其他事情。每一个有用的单子都有一种“特殊的力量”,这使它独一无二。
(没有什么特别作用的monad被称为“身份monad”。与身份函数类似,这听起来是一件毫无意义的事情,但事实证明并非如此……但这是另一回事™.)
基本上,每个monad都有自己的绑定函数实现。你可以编写一个绑定函数,这样它就可以在执行步骤之间做一些傻事。例如:
如果每个步骤都返回一个成功/失败指示符,则只有在前一个步骤成功的情况下,才能让绑定执行下一个步骤。这样,失败的步骤“自动”中止整个序列,而无需您进行任何条件测试。(故障单)扩展这个想法,您可以实现“异常”。(错误单点或异常单点。)因为您自己定义它们,而不是将其作为一种语言特性,所以您可以定义它们的工作方式。(例如,您可能希望忽略前两个异常,仅在引发第三个异常时中止。)您可以使每个步骤返回多个结果,并让bind函数对其进行循环,将每个结果输入到下一步。这样,在处理多个结果时,就不必一直到处写循环。绑定函数“自动”为您完成所有这些。(单子)除了将“结果”从一个步骤传递到另一个步骤之外,还可以让bind函数传递额外的数据。这些数据现在不会显示在源代码中,但您仍然可以从任何地方访问它,而无需手动将其传递给每个函数。(《读者》杂志)您可以这样做,以便可以替换“额外数据”。这允许您模拟破坏性更新,而无需实际执行破坏性更新。(莫纳德州及其堂弟作家莫纳德。)因为您只是在模拟破坏性更新,所以您可以轻松地完成真正的破坏性更新所无法完成的事情。例如,您可以撤消上一次更新,或恢复到旧版本。你可以制作一个可以暂停计算的monad,这样你就可以暂停你的程序,进入并修补内部状态数据,然后恢复它。您可以将“continuations”实现为monad。这可以让你打破人们的想法!
所有这些和更多的都可以通过monad实现。当然,这一切在没有单子的情况下也是完全可能的。使用monad非常简单。
另一种尝试是解释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))
我最喜欢的Monad教程:
http://www.haskell.org/haskellwiki/All_About_Monads
(在谷歌搜索“monad教程”的17万次点击中!)
@斯图:monads的目的是允许您将(通常)顺序语义添加到纯代码中;您甚至可以组合Monad(使用Monad Transformers)并获得更有趣和复杂的组合语义,例如,带有错误处理的解析、共享状态和日志记录。所有这些在纯代码中都是可能的,monad只允许您将其抽象并在模块化库中重用(在编程中总是很好的),并提供方便的语法使其看起来势在必行。
Haskell已经有了运算符重载[1]:它使用类型类的方式与使用Java或C#中的接口的方式非常相似,但Haskell恰好也允许使用非字母数字标记(如+&&和>)作为中缀标识符。如果您的意思是“重载分号”[2],那么在您看来这只是运算符重载。“重载分号”听起来像是黑魔法,自找麻烦(想象一下有进取心的Perl黑客听到了这个想法),但关键是没有monad就没有分号,因为纯函数代码不需要或不允许显式排序。
这一切听起来比实际情况要复杂得多。sigfpe的文章很酷,但使用了Haskell来解释它,这有点无法打破理解Haskell到grok Monads和理解Monads到grok Haskell的鸡和蛋的问题。
[1] 这是与monad不同的问题,但monad使用Haskell的运算符重载特性。
[2] 这也是一个过度简化,因为链接一元操作的运算符是>>=(发音为“bind”),但有语法糖(“do”)允许您使用大括号和分号和/或缩进和换行。
我对monads还是个新手,但我想我会分享一个我觉得读起来很好的链接(带图片!!):http://www.matusiak.eu/numerodix/blog/2012/3/11/monads-for-the-layman/(无隶属关系)
基本上,我从这篇文章中得到的温暖而模糊的概念是monad基本上是适配器,允许不同的函数以可组合的方式工作,即能够将多个函数串起来并混合和匹配它们,而不用担心不一致的返回类型等。因此,当我们尝试制作这些适配器时,BIND函数负责将苹果与苹果、橙子与橙子放在一起。LIFT功能负责使用“较低级别”的功能,并将其“升级”为与BIND功能一起使用并可组合。
我希望我做得对,更重要的是,希望这篇文章对单子有一个有效的观点。如果没有别的话,这篇文章有助于激发我学习更多关于单子叶植物的欲望。