在最近简要回顾了Haskell之后,对于monad本质上是什么,有什么简单、简洁、实用的解释?
我发现,我遇到的大多数解释都很难理解,而且缺乏实际细节。
在最近简要回顾了Haskell之后,对于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))
其他回答
[免责声明:我仍在努力完全了解monads。以下是我目前所了解的情况。如果这是错误的,希望有有知识的人会在地毯上给我打电话。]
Arnar写道:
Monads只是一种包装东西的方法,它提供了对包装好的东西进行操作而不展开的方法。
正是这样。想法是这样的:
你需要一些价值,并用一些附加信息来包装它。就像值是某种类型的(例如整数或字符串)一样,附加信息也是某种类型的。例如,该额外信息可能是“可能”或“IO”。然后,您有一些运算符,允许您在携带附加信息的同时对打包的数据进行操作。这些运算符使用附加信息来决定如何更改包装值上的操作行为。例如,Maybe Int可以是Just Int或Nothing。现在,如果您将Maybe Int添加到Maybe Int,则运算符将检查它们是否都是内部的Just Int,如果是,则将展开Int,将其传递给加法运算符,将生成的Int重新包装为新的Just Int(这是有效的Maybe Int),从而返回Maybe Int。但如果其中一个是内部的Nothing,则该运算符将立即返回Nothing,这也是一个有效的Maybe Int。这样,你可以假装Maybe Ints只是正常的数字,并对它们进行常规运算。如果你得到了一个Nothing,你的方程仍然会产生正确的结果——而不必到处乱检查Nothing。
但这个例子正是Maybe所发生的事情。如果额外的信息是IO,那么将调用为IO定义的特殊运算符,并且在执行添加之前,它可以执行完全不同的操作。(好吧,将两个IO Int加在一起可能是荒谬的——我还不确定。)
基本上,“monad”大致意思是“模式”。但是,您现在有了一种语言构造(语法和所有),可以将新模式声明为程序中的东西,而不是一本充满了非正式解释和专门命名的模式的书。(这里的不精确之处在于所有模式都必须遵循特定的形式,因此monad不像模式那样通用。但我认为这是大多数人都知道和理解的最接近的术语。)
这就是为什么人们觉得单子如此令人困惑:因为它们是一个通用的概念。问是什么使某物成为monad与问是什么让某物成为模式类似。
但是想想在语言中对模式的概念提供语法支持的含义:你不必阅读“四人帮”一书,记住特定模式的构造,只需编写一次代码,以不可知的通用方式实现这个模式,然后就完成了!然后,您可以重用此模式,如Visitor或Strategy或Façade等,只需用它装饰代码中的操作,而无需反复重新实现它!
所以,这就是为什么理解monad的人会发现它们如此有用的原因:这并不是知识势利者以理解为荣的象牙塔概念(好吧,当然也是如此,teehee),而是实际上让代码更简单。
根据我们所谈论的monad,“什么是monad”这个问题是错误的:
对“什么是单单体?”这个问题的简短回答是,它是内函子范畴中的单单体,或者它是一种通用数据类型,配备了满足某些定律的两个运算。这是正确的,但它并没有揭示一个重要的大局。这是因为问题是错误的。在这篇论文中,我们的目标是回答正确的问题,即“当作者谈论单子时,他们真正说的是什么?”
虽然这篇论文没有直接回答什么是单子,但它有助于理解不同背景的人谈论单子时的含义以及原因。
monad实际上是“类型运算符”的一种形式。它将做三件事。首先,它会将一种类型的值“包装”(或以其他方式转换)为另一种类型(通常称为“一元类型”)。第二,它将使底层类型上的所有操作(或函数)在monadic类型上可用。最后,它将为将自身与另一个monad组合以生成复合monad提供支持。
“可能monad”本质上等同于Visual Basic/C#中的“可为null的类型”。它接受不可为null的类型“T”并将其转换为“可为null<T>”,然后定义所有二进制运算符在可为null><T>上的含义。
副作用也有类似的表现。创建了一个结构,该结构包含函数返回值旁边的副作用描述。当值在函数之间传递时,“提升”操作会复制副作用。
它们被称为“monad”,而不是更容易理解的“类型运算符”的名称,原因如下:
Monad对他们的行为有限制(详见定义)。这些限制,加上涉及三个运算,符合范畴理论中一个叫做monad的结构,这是一个模糊的数学分支。它们是由“纯”函数语言的支持者设计的纯函数语言的支持者,如模糊的数学分支由于数学晦涩难懂,而且monad与特定的编程风格相关,人们倾向于使用monad这个词作为一种秘密握手。正因为如此,没有人费心去投资一个更好的名字。
http://mikehadlow.blogspot.com/2011/02/monads-in-c-8-video-of-my-ddd9-monad.html
这是你要找的视频。
用C#演示组合和对齐类型的问题,然后用C#正确实现它们。最后,他展示了F#和Haskell中相同的C#代码的外观。
我最喜欢的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”)允许您使用大括号和分号和/或缩进和换行。