关于haskell:什么是monad?

What is a monad?

在最近短暂地看了哈斯克尔之后,对于单子的本质是什么,一个简短、简洁、实用的解释是什么?

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

  • EricLippert为这个问题(stackoverflow.com/questions/2704652/…)写了一个答案,这是由于一些问题存在于一个单独的页面中。
  • 这里有一个使用javascript的新介绍-我发现它非常易读。
  • 另请看看看单子的不同方式。
  • 也可以在图片中看到单子
  • monad是一个带有助手操作的函数数组。看到这个答案
  • 另一篇类似于sigfpe答案的快速文章:github.com/quchen/articles/blob/master/…
  • 请参阅我的fpcomplete文章,了解您为什么要使用monads(而不是它们的工作方式):bit.ly/mh9zrd
  • 到目前为止,我听到的最好的解释实际上来自维基百科:"monads是可编程的分号。"
  • 我看到了这篇文章:stephanboyer.com/post/9/monads-part-1-a-design-pattern。我发现这是迄今为止向像我这样的外行提供的最好和最有效的概念。作者实际上也为Monad写了其他文章。
  • Douglas Crockford的解释非常简单:youtube.com/watch?V= DKZFTIMGACM
  • 蒙娜德不是哈斯凯尔人特有的概念,@helderpereira。我觉得你的复职不对。
  • @Palec我知道,但这个问题提到了Haskell,而最受欢迎的答案是用Haskell来解释。我只是觉得让学习哈斯克尔的人更容易找到它是有用的,因为这是一个非常重要的语言概念。不过,如果你认为没有道理,你可以自由地把它移走。
  • 我想说Monad是一个结构,它可以让你把所有的垃圾都放进去(做实际的工作,又称副作用),并给你一个花哨的盒子来保持你的代码功能(读副作用免费)。
  • 我喜欢这个问题没有得到明确的答案。
  • Monad是EDSL。看到这个:"有人在某个时刻注意到,"哦,为了从纯代码中获得不纯的效果,我需要做元编程,这意味着我的类型之一需要是"计算x的程序"。我想要一个"计算x的程序"和一个函数,它取x并生成下一个程序,一个"计算y的程序",并以某种方式将它们粘合到一个"计算y的程序"(即绑定操作)。木卫一出生了。"
  • Tomasz Nurkiewicz的文章是针对我发现的Java开发人员的最好解释。


第一:如果你不是数学家,Monad这个词有点空虚。另一个可供选择的术语是计算生成器,它更能描述它们的实际用途。好的。

你需要一些实际的例子:好的。

例1:列表理解:好的。

1
[x*2 | x<-[1..10], odd x]

此表达式返回1到10范围内所有奇数的双精度数。非常有用!好的。

事实证明,对于列表monad中的某些操作,这实际上只是语法上的糖分。同样的列表理解可以写成:好的。

1
2
3
4
do
   x <- [1..10]
   guard (odd x)
   return (x * 2)

甚至:好的。

1
[1..10] >>= (\x -> guard (odd x) >> return (x*2))

例2:输入/输出:好的。

1
2
3
4
do
   putStrLn"What is your name?"
   name <- getLine
   putStrLn ("Welcome," ++ name ++"!")

这两个例子都使用monad,也就是计算生成器。共同的主题是Monad链以某种特定的、有用的方式进行操作。在列表理解中,操作被链接起来,这样,如果一个操作返回一个列表,那么对列表中的每个项目执行以下操作。另一方面,IO monad按顺序执行操作,但同时传递一个"隐藏变量",表示"世界状态",允许我们以纯粹的功能方式编写I/O代码。好的。

结果表明,链接操作模式非常有用,并且在Haskell中用于许多不同的事情。好的。

另一个例子是异常:使用Errormonad,操作被链接,这样就可以按顺序执行,除非抛出了错误,在这种情况下,链的其余部分被放弃。好的。

列表理解语法和do符号都是使用>>=运算符链接操作的语法甜品。monad基本上只是一种支持>>=操作符的类型。好的。

示例3:解析器好的。

这是一个非常简单的解析器,可以解析带引号的字符串或数字:好的。

1
2
3
4
5
6
7
8
9
10
11
parseExpr = parseString <|> parseNumber

parseString = do
        char '"'
        x <- many (noneOf"
"")
        char '"'
        return (StringValue x)

parseNumber = do
    num <- many1 digit
    return (NumberValue (read num))

chardigit等操作相当简单。它们要么匹配要么不匹配。魔力是管理控制流的monad:操作按顺序执行,直到匹配失败,在这种情况下,monad会返回到最新的<|>,并尝试下一个选项。同样,使用一些附加的、有用的语义链接操作的方法。好的。

示例4:异步编程好的。

上面的例子在haskell中,但事实证明f也支持monads。这个例子是从Don Syme那里偷来的:好的。

1
2
3
4
5
6
let AsyncHttp(url:string) =
    async {  let req = WebRequest.Create(url)
             let! rsp = req.GetResponseAsync()
             use stream = rsp.GetResponseStream()
             use reader = new System.IO.StreamReader(stream)
             return reader.ReadToEnd() }

此方法获取网页。穿孔行是使用GetResponseAsync—它实际上在单独的线程上等待响应,而主线程则从函数返回。最后三行在收到响应时在生成的线程上执行。好的。

在大多数其他语言中,您必须为处理响应的行显式地创建一个单独的函数。asyncMonad能够自行"拆分"该块,并推迟后半块的执行。(async {}语法表示块中的控制流是由asyncmonad定义的。)好的。

它们是如何工作的好的。

那么Monad怎么能做所有这些花哨的控制流的事情呢?在do块(或在f中调用的计算表达式)中实际发生的情况是,每个操作(基本上是每一行)都被包装在一个单独的匿名函数中。然后,使用bind运算符(haskell中拼写为>>=)组合这些函数。由于bind操作组合了函数,因此它可以按自己认为合适的方式执行它们:顺序地、多次地、逆向地、丢弃一些函数、在感觉合适的时候在单独的线程上执行一些函数等等。好的。

例如,这是示例2中IO代码的扩展版本:好的。

1
2
3
4
putStrLn"What is your name?"
>>= (\_ -> getLine)
>>= (
ame -> putStrLn ("Welcome," ++ name ++"!"))

这更难看,但实际上发生的事情也更明显。>>=运算符是一个神奇的组成部分:它获取一个值(在左侧)并将其与一个函数(在右侧)组合,以产生一个新的值。然后,下一个>>=运算符获取这个新值,并再次与函数组合以生成新值。>>=可以被看作是一个小型评估器。好的。

注意,不同类型的>>=是重载的,因此每个monad都有自己的>>=实现。(但是,链中的所有操作都必须是同一个monad类型,否则>>=运算符将无法工作。)好的。

>>=的最简单的可能实现就是取左边的值并将其应用到右边的函数并返回结果,但是正如前面所说,当monad的>>=的实现中有额外的事情发生时,整个模式才有用。好的。

在如何将值从一个操作传递到下一个操作上还有一些额外的聪明之处,但这需要对haskell类型系统进行更深入的解释。好的。

总结好的。

在haskell术语中,monad是一个参数化类型,它是monad类型类的一个实例,monad类定义>>=以及一些其他运算符。用外行的术语来说,monad只是定义>>=操作的一种类型。好的。

本身,>>=只是一种链接函数的繁琐方法,但是随着隐藏"管道"的do符号的出现,单元操作被证明是一种非常好和有用的抽象,在语言中的许多地方都很有用,并且对于在语言中创建自己的小型语言非常有用。好的。

为什么单子很难?好的。型

对于哈斯克尔的许多学习者来说,单子是他们遇到的障碍,就像砖墙一样。不是monad本身很复杂,而是实现依赖于许多其他高级的haskell特性,比如参数化类型、类型类等等。问题是haskell I/O是基于monads的,I/O可能是学习一种新语言时首先要了解的内容之一——毕竟,创建不产生任何输出的程序并没有多大乐趣。对于这个鸡和蛋的问题,我没有立即的解决方案,除非把I/O当作"魔法在这里发生",直到你对语言的其他部分有足够的经验为止。对不起的。好的。型

Monads上的优秀博客:http://adit.io/posts/2013-04-17-functors,_applications,_and_monads_in_pictures.html好的。型好啊。

  • 作为一个理解单子有很多问题的人,我可以说这个答案有帮助。一点。但是,还有一些事情我不明白。单子的理解是怎样的?这个例子有一个扩展的形式吗?另一件令我困扰的事情是,大多数单子解释,包括这一个-是他们不断混淆"什么是单子?""单子对什么有好处?""Monad是如何实现的?"当你写"一个monad基本上就是一个支持>>=操作符的类型"的时候,你就跳了。
  • 抓我的头。这似乎是一个实现细节,它并不能真正帮助我回答"为什么我应该使用Monad"这个问题。这可能是真的,但直到那一点的解释还没有让我做好准备。我不是在想"为什么这确实是一个棘手的问题,为什么,我需要一种支持>>=操作符的类型。"哦,嘿,原来这就是单子!"不,那不是在我的头脑中运行,因为我不知道什么是一个>>=操作符,什么是好的,也不知道它解决了什么问题。
  • 当然,你晚些时候做了一点补偿,但恐怕我还是不知道"什么是单子"这个问题的答案,更不用说简单而简洁的解释了。
  • 我也不同意你关于单子为什么很难的结论。如果单子本身并不复杂,那么你应该能够解释它们是什么,而不需要一堆包袱。当我问"Monad是什么"这个问题时,我不想知道实现的情况,我想知道它意味着什么痒。到目前为止,答案似乎是"因为哈斯克尔的作者是虐待狂,并且决定你应该做一些愚蠢复杂的事情来完成简单的事情,所以你必须学习单子使用哈斯克尔,而不是因为他们在任何方面对自己都有用"…
  • 但是…那不可能是对的,对吗?我认为monads很难理解,因为似乎没有人能够在不陷入混乱的实现细节中的情况下找到如何解释它们的方法。我是说…什么是校车?这是一个金属平台,前面有一个装置,它消耗一种精炼的石油产品,在一个循环中驱动一些金属活塞,而金属活塞反过来旋转连接到一些齿轮上的曲柄轴,这些齿轮驱动一些车轮。车轮周围有充气的橡胶袋,橡胶袋与阿什法尔表面接触,使一系列座椅向前移动。座位向前移动是因为…
  • 回答很好,我投你一票。但有一个小细节——要实现monad,类型必须不仅定义了bind操作符(>>=),还定义了返回函数。
  • 我完全同意@breton。您给出了哪些类型的代码使用monad的好例子,但是在imo中,您没有解释monad是什么以及它是如何工作的,这是很重要的。
  • 布雷顿和@MusicFreak,他确实解释了Monad是什么以及它是如何工作的…但出于某种原因,你不能跟随它。我想帮忙,但我已经知道得太多了,因为我在学习曲线上比你领先一点(只是一点点)。如果我们能共同努力找出差距,我会很乐意帮助缩小差距。当然,如果你已经学会了答案,请告诉我。让我回答一个可能是差距一部分的问题——"为什么除了哈斯克尔的其余部分,我们还需要单子?"我们需要单子,因为我们想写一系列的"东西"…
  • (事物只是"代码片段"),并表示这些事物是"连接在一起的",而不必在任何地方重复"连接在一起"在特定情况下的含义。例如,假设您正在处理XML,您需要到达第三个事务元素,到它的第四个lineitem元素,并读取它的quantity属性。足够简单。您甚至可以编写一个xpath来直接访问它。但更严重的事实是,这些项目中的每一项都可能完全丢失,这使情况变得复杂起来。那么,是否还有第三个事务元素?如果文档只有两个事务处理元素怎么办…
  • …或者根本没有?如果它有3个事务元素,但第3个事务元素没有第4行项目元素,该怎么办?最后,如果有一个第四行项目元素,但是该元素没有数量属性怎么办?这会让你头疼,因为你的代码走到了你关心的数量属性的路径上,它被一大堆错误检查代码"乱丢"了,用来处理文档中缺少所需项目的情况。Haskell希望成为一种强大的编程语言,能够解决这些棘手的表达问题…
  • ……为你。在这种情况下,Haskell可以解决这个问题。为什么?因为haskell非常擅长处理匿名函数。如果您给haskell一个匿名函数列表,它可以携带该列表并在以后执行这些函数,并使用不同的参数等。因此,haskell可以允许您编写"路径行走"代码,以获得数量属性,而不必将其与错误检查代码混淆。您将把您的路径遍历代码作为一个匿名函数列表来编写,并且您将使用一个语法来表示,…
  • ……"这个匿名函数被连接到那个函数,这个匿名函数被连接到另一个函数"。另外,在代码中一个完全不同的地方,您定义了"连接到"的含义。在通过XML查找Quantity属性的示例中,"joined to"将被定义为"如果前一个匿名函数找到了它期望找到的XML节点,则继续执行下一个匿名函数"。否则,停止评估所有匿名函数并报告错误。"所以…我想这可以解释…
  • 为什么单子存在于haskell,为什么你想使用单子,以及它们如何工作的一部分(最重要的部分)。如果你把这个和雅克斯的完美答案结合起来,我们会有进展吗?你下一个急迫的问题是什么?
  • 我读了所有这些,但仍然不知道Monad是什么,除了它是Haskell程序员不太明白的东西,不足以解释。这些例子没有多大帮助,因为这些都是没有单子可以做的事情,而这个答案并不能说明单子是如何使它们变得更容易、更混乱的。这个答案中最有用的一部分是删除了示例2的句法成分。我说接近是因为,除了第一行,扩展没有任何真正的相似之处。
  • 另一个似乎是地方性的问题,解释单子是写在哈斯克尔。我不是说哈斯克尔是一种糟糕的语言——我是说这是一种糟糕的语言来解释单子。如果我认识哈斯凯尔,我早就知道蒙纳兹了,所以如果你想解释蒙纳兹,就从使用一种不认识蒙纳兹的人更容易理解的语言开始。如果你必须使用haskell,那就根本不要使用句法上的糖分——使用你所能使用的语言中最小、最简单的子集,并且不要假定你理解haskell-io。
  • 在完成了一个荣誉级别的大学课程,完全致力于功能编程,并被我的讲师宣布为"高级哈斯克尔程序员"…我仍然觉得我只是模糊地知道蒙娜达是什么。我认为问题在于单子是非常抽象的概念(这正是它们如此有用的原因)。我很好地理解很多单子(list、io、maybe、cont、st等);难以捉摸的是,这个东西叫做monad,它以某种方式统一了所有这些概念。
  • 问题是,你不能给出一个清晰的具体例子。因为如果你尝试…它只是列表,或者也许,或者IO。这是很容易掌握的,但不一定有助于不受启发的人看到整个单子。我认为这就是为什么它们很难;它们确实很简单(例如,你必须记住的正确执行和使用单元格的数量远小于你在Java中使用类所需知道的东西)。但是monad的统一概念非常抽象。
  • 单子来源于范畴理论,数学学者将其简单地称为"一般抽象的胡说八道",它们的意思是恭维。
  • @我现在只有时间说一件事,但我希望能有所帮助。你说你不用单子就能做到这一切。是的,你可以。Monad和DSL很相似,因为没有DSL就可以做到。但是它们很好,因为它们可以更容易地表达和维护您要表达的内容。
  • @Charlieflowers对不起,不,那没用。你说单子让事情更容易"表达和维护",没有任何证据支持。相反,我看到很多人不理解他们,以及成堆无用的教程,这些教程表明,即使是所谓的"开明者"也无法很好地理解单子来解释他们。你对DSL的比较也让我怀疑你是在说Do符号而不是Monads。在学习糖之前,我想先了解一下实际的单子。
  • @我认为你把两个不同的东西混为一谈:第一,问题,"我们能证明蒙娜斯的存在吗?"其次,"请帮助我理解Monad是什么。"我在这个答案中的所有评论都是为了帮助(如果只是一个小小的方式)解决问题2。我对讨论问题1(完全正确的问题,只是不是我全部感兴趣的问题)不太感兴趣。因此,我对DSL的评论并不是支持monads的论点,因此提供证据是不合适的。
  • @Charlieflowers你的评论是什么,"一个Monad和一个DSL是相似的,在没有DSL的情况下你可以做到。但是它们很好,因为它们使表达和维护你想要表达的内容更加容易。"如果不是支持Monads的论点的话,这是有意的吗?
  • @正如我所说,这是一个试图回答这个问题的尝试,"请帮助我理解Monad是什么。"你说Monad对于解决人们列出的示例问题是不必要的,我回答说,"没错,他们不是必要的。它们是一个很好的"奖金",有助于提高你的表现力,"这有助于解释蒙娜德在宏伟计划中的地位。它们对于图灵完整性不是必需的,就像DSL不是一样。它们旨在帮助您提高表达能力(就像DSL一样)。
  • @我同意查理的看法。"monad"主要指的是一种接口模式,它可用于以纯粹的功能性方式构造复杂的行为。单子的存在更多是为了方便而不是必要。也许Monad就是一个很好的例子。没有必要。您可以将代码中的if-then-else语句的长序列链接在一起,但谁想这样做呢?因此,不用普通的函数组合运算符,而是使用一个"重载"的函数组合运算符,它自动插入if-then-else内容。此运算符大于或等于。
  • 谢谢,@charlieflowers和op。我想我现在对monad有了一个理解,尽管它是一个相当脆弱的把握。我想只有当我真正开始使用这些概念时,才会有更好的理解。
  • 这个例子是如何与C++ +IO >和<操作符不同的?有什么区别可以解释为什么一个是单子而另一个不是单子?
  • 如果你把Haskell IO函数直接翻译成C++:> =运算符将转换成";",而不是">"或"<"。
  • @ JeremyList,C/C++ EDCOX1×0算子将Haskell EDCOX1×1算子近似翻译。我不认为C或C++有什么类似EDCOX1的2Ω。
  • 如果我错了,请纠正我:monads解决了一个在算术中也显示为divbyzero错误的问题。特别是,一系列相互依赖的计算必须允许出现divbyzero异常。一元解决方案是接受nullable并为"lift"(wrap)现有操作提供函数,以便接受nullable和另一个将数字转换为nullable
  • 如果我错了,请纠正我:monads解决了一个在算术中也显示为divbyzero的问题。特别是,一系列相互依赖的计算必须允许出现divbyzero异常。一元解决方案是接受nullable并提供一个函数来包装现有的运算符,以便允许它们接受nullable(bind)和另一个to life number to nullable(return)。因此,monad是一个容器或扩展类型,具有提升要扩展的目标类型的函数,以及一个包装器,供其操作员接受它们。
  • @乔治,你刚刚描述了江户一〔0〕单子。其他人做不同的事情。解释单子的问题是,每一个单子本身都是非常明显的,但事实上它们有一个共同的结构,这一点一开始可能是违反直觉的。
  • @杰里米我试图提供一种以类型为中心的治疗。例如,一元数可以定义为包含一个divbyzero作为一个值。或者,类似地,状态monad被视为运算符的扩展,以包含状态或上下文参数。等等。然后,bind和return函数成为一元运算符,用于将现有值操作包装或调整为其类型的一元版本。然而,也许一种类型的治疗不够普遍。
  • 如果我认为monad基本上只是一种定义和讨论算法的严格方法,即一组步骤来获得某种结果,那么我是否正确地理解了这一点?我敢肯定这句话忽略了很多错综复杂的东西,但这就是要点吗?
  • 非常感谢。这是一个很好的解释,也是让我看到整个画面的最后一块拼图。o/
  • @JPMC26我同意。我喜欢表达monad的方法是,当您有依赖于前面操作结果的操作时,它们提供了一种抽象出控制流模式的方法。
  • Haskell的一个问题是它使用了任意定义的符号。一个符号可能看起来很明显,就像bind本身的符号一样,>>=,可能会发生很多任意的事情。我在这里讲的是在解析示例中,<>的用法。需要定义。
  • @george-<|>组合器不是来自语言本身,它是在一个名为Parsec的库中定义的,该库用于构建解析器(它严重依赖于单元抽象)。我不知道为什么欧普没有在他的回答中解释这一点。
  • 伙计们,Monads是数学界的。如果你想知道Monad是什么,你必须和一个数学家谈谈。仅仅因为它在某些编程语言中,并不意味着程序员应该解释它是什么。例如,编程语言可以对数字求和,但是如果你想解释求和是什么,你必须和一个数学家谈谈。我希望现在一切都清楚了。我的程序员们,别再解释单子了。
  • 我的理解力已经增强,我正在考虑改写上面的内容。由于目前的这种活动,我想我可能很快就会这么做。@加布里埃尔单子的概念是函数式编程中的一个概念,与它在数学中的位置或起源无关。
  • 我故意说得有点过火,不明白我的意思。当然,一些程序员能够解释和理解Monad是什么——顺便说一句,我不是他们中的一员。
  • 这是一个很好的解释。非常感谢你的努力。:)
  • 有人能解释一下为什么在第一个例子中if odd x起作用,因为x是一个列表。如果我试一下:f=odd [1,2,3],为什么在IO Monad里面不破裂?


解释"Monad是什么"有点像说"数字是什么?"我们总是用数字。但想象一下你遇到了一个对数字一无所知的人。你怎么解释这些数字?你怎么开始描述为什么这可能有用?

什么是单子?简而言之:这是一种将操作链接在一起的特定方法。

本质上,您正在编写执行步骤,并将它们与"绑定函数"链接在一起。(在haskell中,它名为>>=)您可以自己编写对绑定操作符的调用,也可以使用语法sugar让编译器为您插入那些函数调用。但是不管怎样,每个步骤都是通过调用这个绑定函数来分隔的。

所以bind函数就像一个分号;它分隔了进程中的步骤。bind函数的工作是获取上一步的输出,并将其输入下一步。

听起来不太难,对吧?但是有不止一种单子。为什么?怎么用?

好吧,bind函数只需从一个步骤中获取结果,并将其提供给下一个步骤。但如果这就是蒙娜德所做的一切…这实际上不是很有用。这一点很重要,要理解:每一个有用的单子除了做单子之外,还做其他的事情。每一个有用的单子都有一种"特殊的力量",这使它独一无二。

(一个没有什么特别之处的单子被称为"身份单子"。这听起来像是一个毫无意义的东西,但结果却不是…但这是另一个故事&trade;)

基本上,每个monad都有自己的bind函数实现。您可以编写一个绑定函数,这样它就可以在执行步骤之间执行循环操作。例如:

  • 如果每个步骤都返回一个成功/失败指示器,那么只有在前一个步骤成功时,才能让bind执行下一个步骤。这样,失败的步骤将"自动"中止整个序列,而无需您进行任何条件测试。(失败的单子。)

  • 扩展这个概念,您可以实现"异常"。(错误Monad或异常Monad。)因为您自己定义它们,而不是将其作为语言功能,所以您可以定义它们的工作方式。(例如,您可能希望忽略前两个异常,只有在引发第三个异常时才会中止。)

  • 您可以使每个步骤返回多个结果,并让绑定函数循环遍历这些结果,将每个结果送入下一步。这样,在处理多个结果时,就不必一直在各地编写循环。绑定函数"自动"为您完成所有这些。(单子上的单子。)

  • 除了将"结果"从一个步骤传递到另一个步骤之外,绑定函数还可以传递额外的数据。这些数据现在不会显示在源代码中,但是您仍然可以从任何地方访问它,而无需手动将其传递给每个函数。(读者Monad。)

  • 您可以这样做,以便替换"额外数据"。这允许您模拟破坏性更新,而实际上不进行破坏性更新。(州里的蒙娜德和它的表兄,作家蒙娜德。)

  • 因为你只是在模拟破坏性的更新,你可以做一些真正破坏性的更新是不可能的事情。例如,您可以撤消上一次更新,或者恢复到旧版本。

  • 你可以制作一个单子,在那里计算可以暂停,所以你可以暂停你的程序,进入并修补内部状态数据,然后继续它。

  • 您可以将"continuations"实现为monad。这可以让你打破人们的想法!

所有这些和更多的都可以用单子。当然,没有单子,所有这些都是完全可能的。使用Monads非常简单。

  • 我很感激你的回答,尤其是最后的让步:没有单子,所有这些当然也可能。需要指出的一点是,单子的使用更容易,但它通常不如没有单子的使用效率高。一旦您需要使用转换器,函数调用(和创建的函数对象)的额外分层将带来难以看到和控制的成本,通过巧妙的语法将其变为不可见的。
  • 型至少在哈斯克尔,乐观主义者剥夺了monads的大部分开销。因此,唯一真正的"成本"是所需的脑力。(如果你关心"可维护性",这并不重要。)但通常情况下,monads使事情变得更容易,而不是更困难。(否则,你为什么要麻烦?)
  • 型我不确定haskell是否支持这一点,但从数学上来说,您可以定义一个monad,或者用>>=和return,或者join和ap。>>=而返回是使monad实际上有用的,但是join和ap提供了对monad的更直观的理解。
  • 型我上面的评论是美联社:我的意思是挺举
  • 型@JeremyList liftMfmap相同。要定义monad,除了>>=join之外,还需要returnpure来定义monad。
  • 型这个答案来自于一个非数学、非函数的编程背景,对我来说是最有意义的。
  • 型这是第一个让我知道蒙娜德到底是什么的答案。感谢您找到一种解释方法!
  • 型你的回答绝对精彩!多谢了!
  • 型这应该是多数票都能接受的答案。很好的解释!
  • 型@MathematicalOrchid您提到了"将操作链接在一起",这让我想到了jQuery的链接?或者jquery的实现完全不同?这是否也与hocs有关?


事实上,与蒙娜斯的共同理解相反,他们与国家无关。monad只是包装东西的一种方法,它提供了在不拆开包装的情况下对包装的东西进行操作的方法。

例如,可以在haskell中创建一个类型来包装另一个类型:

1
data Wrapped a = Wrap a

包装我们定义的东西

1
2
return :: a -> Wrapped a
return x = Wrap x

要在不展开的情况下执行操作,假设您有一个函数f :: a -> b,那么您可以这样做来提升该函数以对包装值执行操作:

1
2
fmap :: (a -> b) -> (Wrapped a -> Wrapped b)
fmap f (Wrap x) = Wrap (f x)

这就是要理解的一切。然而,事实证明,这种提升有一个更一般的功能,即bind

1
2
bind :: (a -> Wrapped b) -> (Wrapped a -> Wrapped b)
bind f (Wrap x) = f x

bindfmap能做得多一点,但反过来不行。实际上,fmap只能用bindreturn来定义。所以,当定义Monad时……你给出它的类型(这里是Wrapped a,然后说它的returnbind操作是如何工作的。

很酷的是,这是一个非常普通的模式,它会突然出现在所有地方,以一种纯粹的方式封装状态只是其中之一。

关于如何使用monad来引入功能依赖性,从而控制评估顺序的一篇好文章,就像在Haskell的IO monad中使用的一样,请查看内部的IO。

至于了解单子,不要太担心。读一些你觉得有趣的东西,如果你不马上理解的话,不要担心。然后,像哈斯克尔这样的语言潜水就是一条出路。单子就是这样的东西之一,通过练习,理解会慢慢进入你的大脑,有一天你突然意识到你理解了它们。

  • ->是右关联的镜像函数应用程序,它是左关联的,所以将括号去掉在这里不会有什么区别。
  • 你的解释骗了我。我本可以添加一些标准monad(reader,state,maybe,…)的有限求和来说明一些实际用途和包装。
  • 我认为这根本不是一个很好的解释。单子只是一种方式?好的,哪条路?为什么不使用类而不是Monad来封装?
  • 对这个想法的一个较长的解释是:blog.sigfpe.com/2007/04/trivial-monad.html
  • 我们可以用f=Sqrt来代替fmap示例,以便更好地理解。从其他语言中借用符号,fmap示例归结为fmap(f)([x]) means the same as [f(x)](fmap(f)f的"提升"版本,bind示例归结为bind(fThenWrap)([x]) means to the same as fThenWrap(x)版本。我很好奇为什么我们会有像a -> Wrapped b这样的功能?
  • 是不是应该是:fmap :: (a -> b) -> Wrapped a -> Wrapped b?haskell.org/haskellwiki/monads_as_容器
  • @MB21:如果您只是指出括号太多,请注意a->b->c实际上只是a->(b->c)的缩写。将这个特殊的例子写成(a->b->(ta->tb)严格来说只是添加了一些不必要的字符,但在道义上是"正确的事情",因为它强调了fmap将a->b类型的函数映射到ta->tb类型的函数。最初,这就是泛函在范畴理论中的作用,也是单子的由来。
  • @对,他们是一样的。我只是更熟悉用map作为清单来思考这个问题。
  • 这个答案有误导性。一些monad根本没有"包装器",比如来自固定值的函数。


但是,你本可以发明单子的!

sigfpe says:

But all of these introduce monads as something esoteric in need of explanation. But what I want to argue is that they aren't esoteric at all. In fact, faced with various problems in functional programming you would have been led, inexorably, to certain solutions, all of which are examples of monads. In fact, I hope to get you to invent them now if you haven't already. It's then a small step to notice that all of these solutions are in fact the same solution in disguise. And after reading this, you might be in a better position to understand other documents on monads because you'll recognise everything you see as something you've already invented.

Many of the problems that monads try to solve are related to the issue of side effects. So we'll start with them. (Note that monads let you do more than handle side-effects, in particular many types of container object can be viewed as monads. Some of the introductions to monads find it hard to reconcile these two different uses of monads and concentrate on just one or the other.)

In an imperative programming language such as C++, functions behave nothing like the functions of mathematics. For example, suppose we have a C++ function that takes a single floating point argument and returns a floating point result. Superficially it might seem a little like a mathematical function mapping reals to reals, but a C++ function can do more than just return a number that depends on its arguments. It can read and write the values of global variables as well as writing output to the screen and receiving input from the user. In a pure functional language, however, a function can only read what is supplied to it in its arguments and the only way it can have an effect on the world is through the values it returns.

< /块引用>

  • …不仅在互联网上,而且在任何地方都是最好的方式。(我在下面的答案中提到的wadler关于函数式编程的原始论文Monads也是不错的。)数不清的类比教程都没有一个能接近。
  • Sigfpe文章的这个javascript翻译是学习Monads的新的最好方法,对于那些还没有学习高级haskell的人来说!
  • 这就是我学习单子的方法。引导读者通过发明一个概念的过程,通常是教授这个概念的最好方法。
  • 但是,接受屏幕对象作为参数并返回其文本修改后的副本的函数是纯函数。


monad是具有两个操作的数据类型:>>=(aka bind)和return(aka unit)。return采用任意值并创建monad实例。>>=以monad为例,并在其上映射一个函数。(您已经看到monad是一种奇怪的数据类型,因为在大多数编程语言中,您无法编写一个接受任意值并从中创建类型的函数。monads使用一种参数多态性。)

在haskell表示法中,monad接口被写入

1
2
3
class Monad m where
  return :: a -> m a
  (>>=) :: forall a b . m a -> (a -> m b) -> m b

这些操作应该遵守某些"法律",但这并不十分重要:"法律"只是编纂了操作的合理实施方式(基本上,>>=return应该就值如何转换为单实例达成一致,>>=是关联的)。

monad不仅仅与状态和I/O有关:它们抽象了一种通用的计算模式,包括处理状态、I/O、异常和非确定性。可能最简单的单子是列表和选项类型:

1
2
3
4
5
6
7
8
9
instance Monad [ ] where
    []     >>= k = []
    (x:xs) >>= k = k x ++ (xs >>= k)
    return x     = [x]

instance Monad Maybe where
    Just x  >>= k = k x
    Nothing >>= k = Nothing
    return x      = Just x

其中[]:为列表构造器,++为连接运算符,JustNothingMaybe构造器。这两个单体都封装了各自数据类型上的通用和有用的计算模式(注意,两者都与副作用或I/O无关)。

你真的需要花点时间写一些不平凡的haskell代码来理解monads是关于什么以及为什么它们是有用的。

  • 你所说的"在上面映射一个函数"到底是什么意思?
  • 凯斯巴什,我在介绍中故意不客气。请参阅结尾处的示例,了解"映射函数"需要什么。
  • monad不是数据类型。它是组成函数的规则:stackoverflow.com/a/37345315/1614973


你首先应该理解什么是函子。在此之前,请理解高阶函数。

高阶函数只是一个以函数为参数的函数。

函式是存在一个高阶函数的任何类型构造T,称为map,它将a -> b类型的函数(给定ab两种类型)转换为T a -> T b的函数。此map函数还必须遵守同一性和组合律,以便下列表达式对所有pq都返回true(haskell表示法):

1
2
map id = id
map (p . q) = map p . map q

例如,如果一个名为List的类型构造函数配备了一个符合上述规律的(a -> b) -> List a -> List b类型函数,那么它就是一个函数。唯一实际的实现是显而易见的。生成的List a -> List b函数迭代给定的列表,为每个元素调用(a -> b)函数,并返回结果列表。

monad本质上只是一个函数T,有两种额外的方法:joinT (T a) -> T a型和unit型(有时称为returnforkpurea -> T a型。对于haskell中的列表:

1
2
join :: [[a]] -> [a]
pure :: a -> [a]

为什么有用?因为您可以,例如,使用返回列表的函数对一个列表执行mapjoin获取结果列表并将其连接起来。List是单胞菌,因为这是可能的。

您可以编写执行map的函数,然后编写join的函数。此函数称为bindflatMap(>>=)(=<<)。这通常是haskell中给出monad实例的方式。

monad必须满足某些规律,即join必须是关联的。这意味着,如果您有[[[a]]]类型的值x,那么join (join x)应该等于join (map join x)。并且pure必须是join的身份,这样join (pure x) == x就可以了。

  • 稍微增加了"高阶函数"的定义:它们可以接受或返回函数。这就是为什么他们"更高"的原因,因为他们自己做事情。
  • 根据这个定义,加法是一个高阶函数。它接受一个数字并返回一个将该数字添加到另一个数字的函数。所以不,高阶函数是严格的函数,其域由函数组成。
  • 影片《布莱恩贝克曼:不要害怕蒙娜德》遵循了同样的逻辑。


[ disclaimer:我还想充分理解的单子。以下就是我的父亲有understood母猪。如果真的错了,hopefully knowledgeable会有人打电话给我的地毯。

arnar写道:

Monads are simply a way to wrapping things and provide methods to do operations on the wrapped stuff without unwrapping it.

而正是这样的。的想法是这样的:

  • 你把这种价值和把它与一些额外的信息。是的,就像《价值某冰颊(EG。安整数或字符串)的附加信息,所以冰某的脸颊。

    例如,这可能是一个额外的信息MaybeIO

  • 你有一些运营商,允许你操作的数据包。沿这段额外的信息。这些运营商使用的附加信息的两个《决定如何改变行为的操作在包装的价值。

    例如,可以Maybe IntA或A Just IntNothing。现在,如果你添加一Maybe Int两个Maybe Int放映会的检查,如果他们看到的都是Just Ints里面,如果这样,将unwrap《Ints,通过他们的加法操作,重新包装的resulting Int到一个新的Just Int(这是一个有效的Maybe Int),和一Maybe Int这样的回报。但如果他们是一个一个Nothing在里面,这只是运营商将立即返回Nothing了,这是一个有效的Maybe Int。这样的方式,你可以pretend,你是完全正常的Maybe Ints数和perform常规数学对他们。如果你是一个让你Nothing方程,将静态的生产权,没有结果,你有两个站Nothing所有支票。

  • 但以冰为Maybe真的会发生什么。如果我是个IO额外的信息,然后,定义为IOs特殊的放映会,而不是所谓的,它可以做一些完全不同的表演过《之外。(好吧,第二IO Ints增冰可能nonsensical——我不确定,我(也)。注意,如果你支付的Maybeexample,你有noticed"包装,A值与冰的额外的东西",并不总是正确的。但它的硬的精确和准确的,正确的,没有被inscrutable)。

    basically"单子"roughly均值的"模式"。但是,而不是一本书的全鸭informally explained专门命名方式,你现在有一个语言的语法构造和所有————这是允许你为新模式的两个DECLARE的事情在你的程序。(《冰imprecision这里所有的方式有两种,一种特殊的单子,所以A是不通用的,作为一个全新的AA型。但我认为这是closest术语,大多数人知道和了解。)

    这就是为什么人们找到冰渣confusing单子:因为他们是这样一种通用的概念。问:是什么让一个单子的冰similarly浪潮为问是什么让一个A型。

    我觉得这个含义大学具有句法的语言支持的想法(而不是A型:具有两个读《四人帮》和《建设部memorise特别是A型,你只要写代码,implements这个模式在一个通用的agnostic通一次,然后你做了!这是你可以重用的类型、游客或战略或什么的?艾德或什么的,只是在城市作战decorating用它在你的代码中,有两个没有重新实施它的鸭子!

    所以这是为什么中国人了解它们,所以找到有用的单子:这不是象牙塔的一些概念,snobs自豪自己对知识的理解(这也是好的,当然,teehee),但实际上使简单的代码。

    • 有时,来自"学习者"(像你一样)的解释比来自专家的解释更与另一个学习者相关。学习者的想法相似:)
    • 使某物成为单胞菌的是M (M a) -> M a型函数的存在。你可以把它转换成M a -> (a -> M b) -> M b类型,这是它们有用的原因。
    • "monad"大致意思是"模式"…不。


    经过努力,我想我终于明白了单子。在重读了我自己对压倒性的最高投票答案的冗长批评之后,我将给出这个解释。

    要理解单子,需要回答三个问题:

  • 你为什么需要一个单子?
  • 什么是单子?
  • Monad是如何实现的?
  • 正如我在最初的评论中所指出的,太多的单子解释在问题3中被卷入,没有,而且在真正充分涵盖问题2或问题1之前。

    你为什么需要一个单子?

    像Haskell这样的纯函数语言与命令语言(如C或Java)不同,纯函数程序不一定按特定的顺序执行,一次一步。haskell程序更类似于一个数学函数,在这个函数中,你可以用任意数量的势序来解"方程"。这带来了许多好处,其中包括消除了某些类型的错误的可能性,特别是与"状态"等相关的错误。

    但是,有一些问题并不能用这种编程方式直接解决。有些事情,比如控制台编程和文件I/O,需要以特定的顺序发生,或者需要保持状态。处理这个问题的一种方法是创建一种表示计算状态的对象,以及一系列以状态对象为输入的函数,并返回一个新的修改过的状态对象。

    因此,让我们创建一个假设的"状态"值,它表示控制台屏幕的状态。这个值的构造方式并不重要,但我们假设它是一个字节长度的ASCII字符数组,表示屏幕上当前可见的内容,以及一个数组,表示用户输入的最后一行伪代码。我们已经定义了一些接受控制台状态、修改它并返回新控制台状态的函数。

    1
    consolestate MyConsole = new consolestate;

    因此,要进行控制台编程,但是以一种纯粹的函数方式,您需要在每个对象内部嵌套大量的函数调用。

    1
    consolestate FinalConsole = print(input(print(myconsole,"Hello, what's your name?")),"hello, %inputbuffer%!");

    以这种方式编程可以保持"纯粹"的功能风格,同时强制以特定的顺序对控制台进行更改。但是,我们可能希望像上面的例子一样,一次只做几个操作。这样的嵌套函数将开始变得笨拙。我们想要的是基本上和上面一样的代码,但是写得更像这样:

    1
    2
    3
    4
    consolestate FinalConsole = myconsole:
                                print("Hello, what's your name?"):
                                input():
                                print("hello, %inputbuffer%!");

    这确实是一种更方便的书写方式。但我们该怎么做呢?

    什么是单子?

    一旦您定义了一个类型(如consolestate)以及一系列专门针对该类型进行操作的函数,您就可以通过定义一个像:(bind)这样的运算符,将这些内容的整个包转换为一个"monad",该运算符会自动将其左侧的返回值输入到右侧的函数参数中,并且lift运算符,它将普通函数转换为与特定类型的绑定运算符一起工作的函数。

    Monad是如何实现的?

    看看其他的答案,这似乎很容易跳到细节。

    • 排序并不是定义monad的唯一原因。monad只是具有bind和return的任何函数。绑定并返回给您排序。但他们也提供其他东西。另外,请注意,您最喜欢的命令式语言实际上是一个具有OO类的奇特IO monad。使定义monad变得容易意味着很容易使用解释器模式——将DSL定义为monad并解释它!
    • 下面是一个实现:github.com/brianspinos777/programming_-cheat-sheets/blob/mast&zwnj;&8203;er/&hellip;


    (另请参见"Monad是什么?"

    一个很好的单子动机是sigfpe(dan piponi)的单子你可以发明!(也许你已经有了)。有很多其他的单子教程,其中许多错误地试图用各种类比来用"简单的术语"解释单子:这是单子教程的谬误;避免它们。

    正如麦基弗博士在《告诉我们为什么你的语言很烂》一书中所说的:

    So, things I hate about Haskell:

    Let’s start with the obvious. Monad tutorials. No, not monads. Specifically the tutorials. They’re endless, overblown and dear god are they tedious. Further, I’ve never seen any convincing evidence that they actually help. Read the class definition, write some code, get over the scary name.

    你说你理解也许是单子?很好,你在路上。你只要开始使用其他单子,迟早你会明白什么是单子。

    [如果你是以数学为导向的,你可能想忽略几十个教程并学习其定义,或者遵循类别理论中的讲座:)定义的主要部分是monad m包含一个"类型构造函数",它为每个现有类型"t"定义一个新类型"m t",以及在"常规"类型和"m"类型之间来回切换的一些方法。]

    另外,令人惊讶的是,Monads最好的介绍之一实际上是介绍Monads的早期学术论文之一,PhilipWadler的函数编程Monads。它实际上有一些实用的,非琐碎的激励例子,不像外面的许多人工教程。

    • 韦德勒的论文唯一的问题是符号不同,但我同意这篇论文是非常引人注目的,并且是应用单子的一个清晰简洁的动机。
    • +"Monad教程谬误"为1。monads的教程类似于有几个教程试图解释整数的概念。一个教程会说,"1和苹果相似";另一个教程说,"2像梨";第三个教程说,"3基本上是一个橘子"。但你永远无法从任何一个教程中获得全部的信息。我从中得到的是,单子是一个抽象的概念,可以用于许多不同的目的。
    • @斯塔克斯:是的,没错。但我并不是说monads是一个你不能学习或不应该学习的抽象;只是在你看到足够具体的例子来理解一个单一的底层抽象之后,最好去学习它。看看我的另一个答案。
    • 有时我觉得有太多的教程试图说服读者,通过使用复杂或有用的代码,monad是有用的。这妨碍了我好几个月的理解。我不是那样学的。我更喜欢看到非常简单的代码,做一些愚蠢的事情,我可以在精神上通过,我找不到这种例子。我不知道第一个示例是不是monad来解析复杂的语法。我可以学习它是否是一个单精度整数求和。
    • 只提到类型构造函数是不完整的:stackoverflow.com/a/37345315/1614973


    我写这篇文章主要是为了我,但我希望其他人觉得它有用:)好的。

    我相信这个解释更正确。然而,我认为这种治疗仍然是有价值的,并将考虑在以后纳入它。可以这么说,当传统的函数组合处理普通值函数时,monad是关于组合对函数值(高阶函数)进行操作的函数。在处理高阶函数(接受或返回函数的函数)时,必须自定义或定制组合,以便在计算组合时评估操作数。这种评估过程可能很奇特,例如收集异步过程的结果。尽管如此,这种裁剪还是可以按照一定的模式进行。该模式的一个版本称为monad,并遵循非常多的代数加法。特别是,对于以下内容,这种高阶函数将被视为表达式中的数学运算符,接受其他部分应用的运算符,因此,1+ ( 2* ( 3/ ( 7+ (..) ) ) )中的函数1+2*、3/、7+。好的。

    monads处理的问题在算术中也显示为被零除,DivByZero。具体地说,涉及除法的计算必须检测或允许DivByZero例外。这一要求使得在一般情况下对此类表达式进行编码变得混乱。好的。

    一元解决方案是通过执行以下操作来接受DivByZero。好的。

  • 扩展Number类型,将DivByZero作为一个非常规数字的特定值包括:NaNInfinityNull。我们称这个新的数字类型为Nullable
  • 提供"提升"或将现有的Number包装成Nullable类型的功能("包装"的概念是内容Number或值可以"拆开"而不会丢失信息)。
  • 提供"提升"功能或将Number上的现有操作员包装成在Nullable上运行的版本。这样的结果"提升"操作员可能只会执行以下操作:
  • unwrap提供Nullable个操作数,并将其包含的Number个操作数应用于这些操作数,然后将生成的Number个结果"提升"为Nullable
  • 在评估和旁路进一步评估期间检测DivByZero操作数或异常,生成DivByZero值作为结果断言(1 + Null = Null)。然而,要采取什么行动取决于程序员。一般来说,这些包装器函数是编写monad的许多功能的地方。一元状态信息保存在包装器类型本身中,包装函数从中检查,并按照函数编程不可变性方法构造新的一元值。在Nullable的情况下,这种单态信息将描述DivByZero或实际Number是否存在。
  • 因此,monad是一个扩展类型,它包含一个将原始类型"包装"到这个扩展版本的函数,以及另一个包装原始运算符的函数,这样它们就可以处理这个新的扩展类型。(monads可能是泛型或类型参数的动机。)好的。

    事实证明,monad处理不仅可以平滑处理DivByZero(如果可以的话,可以是无穷大),而且可以广泛应用于从类型扩展中受益的情况,以简化编码。事实上,这种适用性似乎很广。好的。

    例如,从字面上看,IO Monad是一种代表宇宙的类型。其目的是认识到原型HelloWorld程序返回的值并不是完全由string的结果类型及其值"hello world"描述的。.实际上,这样的结果还包括对控制台等设备的硬件和内存状态的修改。例如,执行之后,控制台现在显示额外的文本,光标位于新行上,以此类推。如果你愿意的话,IO Monad仅仅是对这种外部效应或副作用的明确认识。好的。

    何苦?好的。

    monad允许设计严格的无状态算法,并记录状态完整的算法。全状态机器很复杂。例如,只有10位的机器可能处于2^10的可能状态。消除多余的复杂性是函数语言的理想选择。好的。

    变量保持状态。消除"变量"应该很简单。纯功能程序不处理变量,只处理值(尽管haskell文档中使用了"variable"一词),而是根据需要使用标签、符号或名称来处理这些值。因此,与纯函数语言中的变量最接近的是函数在每次调用时接受新值时接收的参数。(标签指的是一个值,而变量指的是一个值所在的位置。因此,可以修改变量的内容,但标签是内容本身。最终,最好是给一个苹果,而不是一个装着苹果的袋子。)好的。

    没有变量是纯函数语言使用递归而不是循环进行迭代的原因。递增计数器的操作涉及到使用一个变为递增的变量,以及所有不确定因素,包括如何更新、测试时、它应该是什么值以及何时更新,以及当多个线程可能访问同一个变量时的复杂性。好的。

    不过,那又怎样?好的。

    如果没有国家的存在,一个函数就必须成为一个结果的声明或定义,与某个潜在国家对一个结果的登记相反。本质上,incFun(x) = x + 1的函数表达式比incImp(x) = x.add(1); return x;的命令表达式简单,这里,incFun不修改x而是创造一个新的值。甚至可以用它在表达式中的定义来代替incfun,比如1 + incFun(x)变为1 + (x + 1)。另一方面,incImp修改了x的状态。对于x而言,无论这种修改意味着什么,都可能是不清楚的,并且最终除了任何并发问题之外,如果不执行程序,就无法确定。好的。

    随着时间的推移,这种复杂性的认知代价越来越高(2^n)。相反,操作者+不能修改x而是必须构造一个新值,其结果仅限于并完全由x1的值和+的定义决定。特别是,避免了2^n复杂性爆炸。此外,为了强调并发性,与incFun不同,incImp不能在没有关于参数共享的预防措施的情况下并发调用,因为它会被每次调用修改。好的。

    为什么叫它Monad?好的。

    单子的特点是一个数学结构称为单子从代数群理论。这就是说,它的意思是一个单体具有以下三个性质:好的。

  • 具有二元运算符*,因此x, y, and zx * y = z属于某种类型的S。例如1÷;2=0.5,其中1、2和0.5都是Number类型。关闭
  • 有一个标识元素,i,与二元运算符关联,该二元运算符不执行任何操作,因此(i * x) = (x * i) = x。例如,数字运算符"+"和数字"0",在4+0=0+4=4中。身份
  • "分段"的评价顺序是不相关的:(x * y) * z = x * (y * z)。例如,(3 + 4) + 12 = 3 + (4 + 12) = 19中的数字运算符"+"。但是请注意,术语的顺序不能改变。结合性
  • 属性3(关联性)允许通过将任意长度的表达式划分为段并单独计算每个段(如并行)来计算。例如,x1*x2*...*xN可以分割成(x1..xJ) * (xJ+1...xM) * (xM+1...xN)。然后可以收集单独的结果x0J * xJM * xMN,并进行类似的进一步评估。支持这样的分割是一项关键技术,可以确保谷歌的分布式搜索算法(LA map/reduce)使用的正确并发性和分布式评估。好的。

    属性2(标识)允许以各种方式更容易地构造表达式,尽管它可能并不完全明显;但是,同样地,对于早期计数系统来说,零显然不是必需的,它作为空的概念和包装空值一样有用。注意,在类型Nullable中,Null不是空值,而是DivByZero的值。具体来说,nn + DivByZero = DivByZeronn + 0 = 0 + nn = nn因此0仍然是+下的身份,其中nn是任何Nullable下的身份。好的。

    最后,我们不再使用罗马数字是有原因的……没有对零或分数、无理数、负数、虚数的扩展适应……是的,似乎我们的数字系统可以被视为单元数。好的。好啊。

    • 型"…但是我希望其他人发现它有用"它确实对我有用,尽管有所有强调的句子:d
    • 型这是我读过/看过/听过的单子中最简洁明了的解释。谢谢您!
    • 型单子和单子之间有重要的区别。monad是在不同类型之间"组合"函数的规则,因此它们不构成monoid所需的二进制操作,有关详细信息,请参阅此处:stackoverflow.com/questions/2704652/&hellip;
    • 型对。你是对的。你的文章超出了我的想象:)。然而,我发现这种治疗方法非常有用(并将其添加到我的治疗中,作为对其他人的指导)。谢谢你的提醒:stackoverflow.com/a/7829607/1612190
    • 型你可能把代数群理论和单子的范畴理论混淆了。前者是代数群理论,与代数群无关。
    • 型@Dmitri Zitsev我最近对Monads更熟悉,并考虑修改我的治疗方法。你的评论会激励我的。我想我现在得到了正确的治疗


    monad实际上是"类型运算符"的一种形式。它可以做三件事。首先,它将"包装"(或以其他方式转换)一个类型的值到另一个类型(通常称为"单类型")。其次,它将使底层类型上的所有操作(或函数)都在一元类型上可用。最后,它将支持将自己与另一个单子结合起来生成复合单子。

    在VisualBasic/C中,"maybe monad"本质上等同于"可以为空的类型"。它接受一个不可为空的类型"t",并将其转换为"nullable",然后定义所有二进制运算符在nullable上的含义。

    副作用的表现类似。创建了一个结构,它将副作用的描述与函数的返回值放在一起。然后,"提升"操作在函数之间传递值时复制副作用。

    它们被称为"monads",而不是更容易理解"类型运算符"的名称,原因有以下几种:

  • 单子对他们能做什么有限制(详见定义)。
  • 这些限制,加上涉及三个运算的事实,符合范畴论中被称为单子的东西的结构,这是数学中一个模糊的分支。
  • 它们是由"纯"功能语言的支持者设计的。
  • 纯函数语言的拥护者,如数学的模糊分支
  • 因为数学是模糊的,monad与特定的编程风格相关,人们倾向于使用monad这个词作为一种秘密的握手。正因为如此,没有人费心投资一个更好的名字。
    • monad不是"设计的",它们是从一个领域(类别理论)应用到另一个领域(纯函数式编程语言中的I/O)。牛顿"设计"了微积分吗?
    • 以上第1点和第2点是正确的和有用的。第4点和第5点有点像自动寻的,即使或多或少是正确的。它们对解释单子没什么帮助。
    • 也许monad并不真正等同于nullable。更准确地说,类型可能t等于nullable或多或少(除了haskell中的所有类型在默认情况下都不可为nullable(有用!)例如,所有字符串都是非空的);例如,在c nullable中,不编译它,因为不管您喜欢与否,它总是可以为空的),但是使用maybe作为monad比使用c with nullable更普遍;使用monad的任何代码都可以使用maybe monad,但不存在等效的抽象,并且在c中是惯用的。
    • 回复:4,5:"秘密握手"是一条红鲱鱼。编程充满了行话。哈斯克尔只是不假思索地称之为"物是人非"。如果它已经存在于数学中,为什么要为它起一个新的名字呢?名字并不是人们没有单子的原因,它们是一个微妙的概念。一般人可能理解加法和乘法,为什么不理解阿贝尔群的概念呢?因为它更抽象、更一般,而且这个人还没有做过将他们的头脑围绕这个概念的工作。改名没用。
    • "最后,它将提供将自身与另一个单体结合以生成复合单体的支持。"您是在谈论>>=还是单体变压器?您是说"一个monad提供了一种组合同一类型的两个monadic操作(bind或>==)"的方法吗?或者"单子通常提供一种将自己与其他单子分层的方式,就像单子变形金刚一样"?你在这里的语言是草率和混乱的,可能会误导。
    • 叹息…我不会攻击哈斯克尔…我在开玩笑。所以,我真的不太明白自己是什么样的人。是的,微积分是"设计的"。这就是为什么,例如,微积分学生被教授莱布尼兹符号,而不是那些讨厌的东西,Netton使用。更好的设计。好的名字有助于理解很多。如果我把阿贝尔群体称为"膨胀的皱纹荚",你可能很难理解我。你可能会说"但是这个名字是胡说八道",没有人会这么称呼他们。对于那些从未听说过范畴理论的人来说,"单子"听起来像是胡说八道。
    • @史考特:对不起,如果我的广泛评论让我觉得我对哈斯克尔有点自卫。我喜欢你对秘密握手的幽默,你会注意到我说的或多或少是真的。:-)如果你把阿贝尔群称为"膨胀的皱纹荚",你会犯同样的错误,试图给单子起一个"更好的名字"(参见f"计算表达式"):这个术语存在,关心单子的人知道什么是单子,但不知道什么是"温暖的模糊事物"(或"计算表达式")。如果我正确理解您对"类型运算符"一词的使用,那么除了monads之外还有许多其他类型的运算符。
    • 5:一个名字只是一个名字:D,但老实说,我不明白人们怎么会想出一个更好的名字,这个名字在某种程度上有意义,而不回避蒙娜达的概念。
    • @斯科特:你好像在说"皱巴巴的豆荚"比标准名称"亚伯兰集团"更糟糕。但你也说化名可能比标准名"monad"更重要?
    • 我是说"monad"的标准名称对大多数人来说是不可接近的。它是由熟悉范畴理论的类型理论家选择的。对他们来说有意义的东西,以及普通人所能接受的东西,都是非常不同的。
    • @斯科特:"亚伯兰集团"的标准名称对大多数人来说也是不可接近的。我看不出有什么不同。
    • 我没有说"亚伯兰集团"是一个平易近人的名字。我用"皱巴巴的豆荚"代替了"阿贝尔群",来指出像"monad"这样的名字对普通人来说是多么的陌生。
    • @史考特:所以你实际上是在提议重命名"阿贝尔集团",也就是说,对于人们还没有学会的每一个抽象概念,我们必须发明新的名字,这应该有助于解决问题。-)
    • 不,我是说程序员不必理解类别理论,单子是完全理解的编程概念,没有类别理论,用类别理论包装它们只会使它们混淆。关于抽象代数中使用的名字,我并不主张任何东西。
    • @斯科特:我们在绕圈子。这里有一个(单子,范畴理论)和(阿贝尔群,代数)之间的完全平行。你同意吗?(假设你这样做…)在任何一种情况下,都可以在不学习整个理论的情况下理解成对的第一个元素的相关概念(例如,任何人都可以在不了解整个群理论的情况下理解阿贝尔群是什么),但这不是重命名术语的原因。(或者是,根据你的说法?)(顺便说一句,请在您的邮件前面加上"@shr…",否则我将不会收到通知。)
    • @史里瓦察不,他们不一样。阿贝尔群不是设计模式。这是一个数学结构。在范畴理论中,单子也是数学结构。我的观点不是说数学结构的名称有问题。我的观点是,用数学结构来命名设计模式是不好的。
    • @斯科特:我还是看不出有什么区别……但是,也许最好弄清问题的症结:你能推荐一个比"Monad"更好的名字吗?(由于Jared已经提到的原因,"类型运算符"是一个糟糕的选择。)
    • @史里瓦斯塔当然。""装饰师"是首先想到的。它传达了Monad所做的以及它如何工作的一些含义。它可能不像"Monad"那样精确,但我认为这是一件好事。
    • 我觉得斯科特很有道理。"monoid"这个词真的让我恼火,所以我贴了一个关于它起源的问题,得到了一个很好的答案。stackoverflow.com/questions/14090183/&hellip;似乎如果haskell没有获得abel组,那么"膨胀的皱纹荚"仍然可以作为monads的改进名称使用:-)。
    • "符合范畴论中一个被称为单子的东西的结构,它是数学中一个模糊的分支。"为什么它是"模糊的"?因为这些概念只有70年的历史,从来没有进入过学校的课程,所以人们一般都知道吗?一旦你花了几天时间学习自然转换——三重结构的两个部分(返回和连接)组成了一个单胞结构——你就会发现它们确实是所有功能中美好而自然的部分。同时,MP3格式、U盘或术语"foobar"都是非常特别的,并不自然。人造的


    monad用于控制数据流的抽象数据类型。

    换句话说,许多开发人员对集合、列表、字典(或散列或映射)和树的概念都很满意。在这些数据类型中,有许多特殊情况(例如insertionorderPreventingIdentityHashMap)。

    然而,当面对程序"流"时,许多开发人员没有接触到比if、switch/case、do、while、goto(grr)和(可能)闭包更多的构造。

    因此,monad只是一个控制流结构。替换monad的更好的短语是"control type"。

    因此,monad具有用于控制逻辑、语句或函数的插槽——数据结构中的等效项是,某些数据结构允许您添加和删除数据。

    例如,"if"monad:

    1
    if( clause ) then block

    最简单的有两个槽——一个子句和一个块。ifmonad通常用于评估子句的结果,如果不是false,则评估块。许多开发人员在学习"if"时并没有被介绍给monad,而只是不需要理解monad来编写有效的逻辑。

    monad可以变得更复杂,就像数据结构变得更复杂一样,但是monad有许多广泛的类别,它们可能具有相似的语义,但实现和语法不同。

    当然,数据结构的迭代或遍历方法与单体的计算方法相同。

    编译器可能支持或不支持用户定义的monad。哈斯克尔当然会。虽然monad这个词在语言中没有使用,但ioke有一些类似的功能。


    我最喜欢的Monad教程:

    http://www.haskell.org/haskellwiki/all-about-monads

    (在谷歌搜索"Monad教程"的17万次点击中!)

    @stu:monads的要点是允许您在其他纯代码中添加(通常)顺序语义;您甚至可以撰写monads(使用monad transformers),并获得更有趣和复杂的组合语义,例如,使用错误处理进行解析、共享状态和日志记录。所有这些在纯代码中都是可能的,monads只允许您将其抽象出来并在模块化库中重用(在编程中总是很好),并提供方便的语法使其看起来很必要。

    Haskell已经拥有了操作符重载(1):它使用的类型类型与Java或C语言中使用接口的方式非常相似,但是Haskell恰好也允许使用非字母数字令牌,例如+&AMP和>作为中缀标识符。如果您的意思是"重载分号"[2],那么它只是一个重载操作符。这听起来像是一种黑色的魔法,它要求麻烦"过载分号"(图片中有进取心的Perl黑客了解这个想法),但关键是没有monad就没有分号,因为纯粹的函数代码不需要或允许显式排序。

    这听起来比需要的要复杂得多。Sigfpe的文章很酷,但是用haskell来解释它,这类文章没有打破理解haskell去呻吟monads和理解monads去呻吟haskell的鸡和蛋的问题。

    [1]这是与Monads不同的问题,但Monads使用了Haskell的运算符重载功能。

    [2]这也过于简单化了,因为链接一元动作的操作符是>>=(发音为"bind"),但是有语法糖("do"),允许您使用大括号、分号和/或缩进和换行符。


    最近我对单子的看法不同。我一直认为它们是以数学的方式抽象出执行顺序,这使得新的多态性成为可能。

    如果您使用的是命令式语言,并且您按顺序编写了一些表达式,那么代码总是按该顺序运行。

    在简单的情况下,当您使用monad时,感觉是一样的——您定义了一个按顺序发生的表达式列表。除此之外,根据您使用的Monad,您的代码可能按顺序运行(如在IO Monad中),同时在多个项目上并行运行(如在列表Monad中),它可能中途停止(如在Maybe Monad中),它可能中途暂停直到稍后恢复(如在恢复Monad中),它可能从一开始就倒带(如在事务monad中),或者它可能会中途倒带以尝试其他选项(如在逻辑monad中)。

    因为monad是多态的,所以可以在不同的monad中运行相同的代码,这取决于您的需要。

    另外,在某些情况下,可以将monad与monad变压器结合起来,同时获得多个功能。


    我对单子还是很陌生,但我想我会分享一个我发现非常适合阅读的链接(带图片)。!)http://www.matusiak.eu/numerodix/blog/2012/3/11/monads-for-the-layman/(无隶属关系)

    基本上,我从本文中得到的温暖和模糊的概念是monad基本上是适配器的概念,它允许不同的函数以可组合的方式工作,即能够将多个函数串起来并混合和匹配它们,而不必担心返回类型不一致等等。因此,bind函数负责在我们试图制造这些适配器时,将苹果与苹果、桔子与桔子放在一起。提升功能负责取"低级"功能并"升级",与绑定功能一起工作,也可组合。

    我希望我做的对,更重要的是,希望这篇文章对monads有一个有效的观点。如果没有别的,这篇文章有助于激发我学习更多关于monads的兴趣。

    • 型python的例子很容易理解!谢谢分享。


    除了上面的优秀答案之外,我还为您提供以下文章(Patrick Thomson)的链接,该文章通过将概念与JavaScript库jQuery(及其使用"方法链接"来操作DOM的方法)相关联来解释Monads:jquery是monad

    jquery文档本身并不引用术语"monad",而是讨论可能更熟悉的"构建器模式"。这并不能改变这样一个事实,那就是你有一个合适的单子,也许你甚至没有意识到。

    • 如果您使用jquery,这个解释会非常有用,特别是如果您的haskell不强
    • jquery显然不是monad。链接的文章错误。
    • "强调"不是很有说服力。有关该主题的一些有用讨论,请参见Isjquerya monad-堆栈溢出
    • 另请参见Douglas Crackford的Google Talk Monads和Gonads,以及他做模态的javascript代码,扩展了Ajax库和承诺的类似行为:DouglasCrockford/Monad&183;Github


    正如丹尼尔·斯皮瓦克解释的那样,单子并不是隐喻,而是一种从普通模式中产生的实际有用的抽象。


    在实践中,monad是函数组合运算符的自定义实现,它处理副作用和不兼容的输入和返回值(用于链接)。


    monad是将共享公共上下文的计算组合在一起的一种方法。这就像是建立一个管道网络。在构建网络时,没有数据流过它。但当我将所有的位与"bind"和"return"混合在一起之后,我调用了类似于runMyMonad monad data的东西,数据通过管道流动。

    • 这比蒙娜德更具应用性。对于monads,在选择下一个要连接的管道之前,必须从管道中获取数据。
    • 是的,你描述的是应用性,而不是单子。monad是,根据到达该点的数据,在管道内部就地构建下一个管段。


    这个答案以一个激励性的例子开始,通过这个例子工作,派生出一个monad的例子,并正式定义"monad"。

    考虑这三个伪代码函数:

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

    f采用形式的有序对,并返回有序对。它使第一个项目保持原样,并将"called f."附加到第二个项目。同g

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

    1
    2
    3
    4
      f(g(wrap(x)))
    = f(g(<x,"">))
    = f(<x,"called g.">)
    = <x,"called g. called f.">

    您不喜欢这样一个事实:fg负责将自己的日志消息附加到以前的日志信息中。(为了论证的目的,假设fg必须在对的第二项上执行复杂的逻辑,而不是附加字符串。在两个或多个不同的函数中重复这种复杂的逻辑是很痛苦的。)

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

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

    但是看看当你创作它们时会发生什么:

    1
    2
    3
    4
      f(g(wrap(x)))
    = f(g(<x,"">))
    = f(<<x,"">,"called g.">)
    = <<<x,"">,"called g.">,"called f.">

    问题是,将一对传递给函数并不能满足您的需要。但是,如果你能将一对输入到一个函数中呢:

    1
    2
    3
    4
      feed(f, feed(g, wrap(x)))
    = feed(f, feed(g, <x,"">))
    = feed(f, <x,"called g.">)
    = <x,"called g. called f.">

    feed(f, m)读作"将m送入f"。把一对送入函数f就是把x送入f中,把f中取出,返回

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

    注意,当你对你的函数做三件事时会发生什么:

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
      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)

    这与向函数传递值相同。

    第二:如果你把一对输入到wrap中:

    1
    2
    3
    4
    5
    6
    7
      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中:

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

    给它喂一对:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
      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是什么类型的值。如果xt型,那么您的对就是"t和string"型的值。称之为M t型。

    EDOCX1 9是一种类型的构造函数:EDCOX1,9,不单独引用一个类型,但是EDCOX1×32 }是用一个类型填充空白之后的一个类型。M int是一对int和一个字符串。M string是一对字符串和一个字符串。等。

    恭喜你,你创造了一个单子!

    正式来说,你的单子是tuple

    monad是一个tuple ,其中:

      百万千克1m是一个类型构造函数。百万千克1百万千克1feed接受(接受t并返回M u的函数)和M t并返回M u的函数。百万千克1百万千克1wrap接受v并返回M v。百万千克1

    tuv是可能相同或可能不同的三种类型。monad满足您为特定monad证明的三个属性:

      百万千克1

      将包装好的t送入函数与将未包装的t传入函数相同。

      正式名称:feed(f, wrap(x)) = f(x)

      百万千克1百万千克1

      M t喂入wrapM t没有任何作用。

      正式名称:feed(wrap, m) = m

      百万千克1百万千克1

      把一个M t(称为m)送入一个函数

        百万千克1将t传递到g中百万千克1百万千克1从g获取M u(称为n)百万千克1百万千克1将n送入f。百万千克1

        百万千克1将m送入g中百万千克1百万千克1从g获取n。百万千克1百万千克1将n送入f。百万千克1

      形式上:feed(h, m) = feed(f, feed(g, m)),其中h(x) := feed(f, g(x))

      百万千克1

    一般情况下,feed称为bind(哈斯克尔称为>>=),wrap称为return


    在scala的上下文中,您会发现以下是最简单的定义。基本上,平面图(或绑定)是"关联的",并且存在一个标识。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    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
      }
    }

    例如。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    // 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的定义与范畴论中monad的定义不同,后者依次由mapflatten定义。尽管它们在某些映射下是等价的。这个演示非常好:http://www.slideshare.net/samthemonad/monad-presentation-scala-as-a-category


    如果我理解正确,IEnumerable是从monads派生出来的。我想知道对于我们这些来自C世界的人来说,这是否是一个有趣的角度?

    值得一提的是,这里有一些教程的链接可以帮助我(不,我仍然不知道Monads是什么)。

    • http://ostele.com/archives/2007/12/overloading-分号
    • http://spbhug.folding-maps.org/wiki/monadsen
    • 网址:http://www.loria.fr/~kow/monads/

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

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

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

    另一方面,如果我们有一个可以发射导弹的功能,我们可以把它与其他也可以发射导弹的功能结合起来,因为我们的意图是明确的——我们想发射导弹——但是它不会因为某些奇怪的原因而尝试打印"你好世界"。好的。

    在Haskell中,main的类型是io()或io[()],其区别很奇怪,我不会讨论它,但我认为会发生以下情况:好的。

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

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

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

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

    对于monads,我能想到的最大原因是——看一下过程/oop代码,你会发现你不知道程序从哪里开始,也不知道程序从哪里结束,你所看到的只是大量的跳跃和大量的数学、魔术和导弹。您将无法维护它,如果可以的话,您将花费大量时间围绕整个程序进行思考,然后才能理解其中的任何部分,因为在此上下文中的模块化是基于代码的跨部门"部分",在这些部分中,代码被优化为尽可能相关,以保证效率/相互关系。.monads是非常具体的,并且通过定义进行了很好的定义,并确保程序流可以进行分析,并隔离难以分析的部分,因为它们本身就是monads。一个单子似乎是一个"可理解的单位,完全理解后是可以预测的"--如果你理解"可能"的单子,除了"可能"之外,它是不可能做任何事情的,它看起来微不足道,但在大多数非单子代码中,一个简单的功能"地狱世界"可以发射导弹,什么都不做,或者摧毁宇宙,甚至是远离宇宙。或者时间——我们既不知道也不保证它是什么。Monad保证它就是它。这是非常强大的。好的。

    "现实世界"中的所有事物似乎都是单子,从某种意义上说,它受到防止混淆的明确可观察的规律的约束。这并不意味着我们必须模仿这个对象的所有操作来创建类,相反,我们可以简单地说"一个正方形就是一个正方形",除了一个正方形,甚至不是一个矩形或一个圆,而且"一个正方形的面积等于它的一个现有维度的长度乘以它自己"。不管你有什么样的正方形,如果它是二维空间中的正方形,那么它的面积绝对不能是任何东西,除了它的长度平方,证明它几乎是微不足道的。这是非常强大的,因为我们不需要做出断言来确保我们的世界是这样的,我们只是利用现实的含义来防止我们的程序偏离轨道。好的。

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


    这两件事对我的学习起到了最大的帮助:

    第8章,"函数解析器",来自GrahamHutton在Haskell的书编程。实际上,这根本没有提到monad,但是如果您能够通读本章并真正理解其中的所有内容,特别是如何评估一系列绑定操作,那么您将了解monad的内部。希望这需要多次尝试。

    教程都是关于Monads的。这给出了几个很好的例子来说明它们的用法,我不得不说,在Appendex中,这个类比是我为自己工作的。


    我将试着在Haskell的背景下解释Monad

    在函数编程中,函数组合非常重要。它允许我们的程序由小的、易于阅读的函数组成。

    假设我们有两个函数:g :: Int -> Stringf :: String -> Bool

    我们可以做(f . g) x,这和f (g x)是一样的,其中xInt值。

    当进行组合/将一个函数的结果应用到另一个函数时,类型匹配非常重要。在上述情况下,g返回的结果类型必须与f接受的类型相同。

    但有时值在上下文中,这使得排列类型变得不那么容易。(在上下文中拥有值是非常有用的。例如,Maybe Int类型表示可能不存在的Int值,IO String类型表示由于执行一些副作用而存在的String值。)

    假设我们现在有g1 :: Int -> Maybe Stringf1 :: String -> Maybe Boolg1f1分别与gf非常相似。

    我们不能做(f1 . g1) xf1 (g1 x),其中xInt值。g1返回的结果类型不是f1预期的类型。

    我们可以用.操作符组合fg,但现在不能用f1g1组合.。问题是我们不能直接将上下文中的值传递给期望值不在上下文中的函数。

    如果我们引入一个操作符来组成g1f1,这样我们就可以编写(f1 OPERATOR g1) x,这不是很好吗?g1返回上下文中的值。该值将脱离上下文并应用于f1。是的,我们有这样一个接线员。是<=<

    我们还有一个>>=操作符,它对我们的作用完全相同,不过语法略有不同。

    我们写信给:g1 x >>= f1g1 x是一个Maybe Int值。>>=运算符帮助将Int值从"可能不存在"上下文中去掉,并将其应用于f1中。f1Maybe Bool的结果,是整个>>=操作的结果。

    最后,为什么Monad有用?因为Monad是定义>>=运算符的类型类,与定义==/=运算符的Eq类型类非常相似。

    综上所述,Monad类型类定义了>>=运算符,它允许我们将上下文中的值(我们称为这些单值)传递给不期望上下文中的值的函数。上下文将得到处理。

    如果这里有一件事需要记住,那就是Monad允许在上下文中包含值的函数组合。

    • 型下面是一个实现:github.com/brianspinos777/programming_-cheat-sheets/blob/mast&zwnj;&8203;er/&hellip;
    • 型iow,monad是广义函数调用协议。
    • 型我认为你的回答是最有帮助的。尽管我不得不说,我认为重点应该放在这样一个事实上:你所指的函数不仅涉及上下文中的值,而且还积极地将值放在上下文中。例如,一个函数f::m a->m b很容易与另一个函数g::m b->m c组合在一起。但是monads(bind-specific)允许我们永久地组合将其输入放在同一上下文中的函数,而不需要我们先从该上下文中取出值(这将有效地从值中删除信息)。
    • 型@詹姆斯,我认为这应该是对函子的强调吗?
    • @乔纳斯,我想我没解释清楚。当我说函数将值放在上下文中时,我的意思是它们有类型(A->M B)。这是非常有用的,因为将一个值放入一个上下文会给它添加新的信息,但通常很难将a(a->m b)和a(b->m c)链接在一起,因为我们不能只从上下文中提取值。因此,我们必须使用一些复杂的过程,根据特定的上下文,以一种合理的方式将这些函数链接在一起,而monad只允许我们以一致的方式来这样做,不管上下文如何。


    http://code.google.com/p/monad-tutorial/是一个正在进行中的工作,以解决这个问题。

    • 看看这是否有助于projects.tmorris.net/public/monad的意思是什么/artures/1&zwnj;&35; 8203;.1/&hellip;
    • 谷歌代码将于2016年1月15日关闭。截至2015年8月24日,大多数项目都是只读的。


    世界需要的是另一篇Monad博客文章,但我认为这对于识别野外现存的Monad很有用。

    • 单子是分形的

    Sierpinski triangle

    The above is a fractal called Sierpinski triangle, the only fractal I can remember to draw. Fractals are self-similar structure like the above triangle, in which the parts are similar to the whole (in this case exactly half the scale as parent triangle).

    Monads are fractals. Given a monadic data structure, its values can be composed to form another value of the data structure. This is why it's useful to programming, and this is why it occurrs in many situations.

    • 型你的意思是"世界不需要什么…"?很好的类比!
    • 型@格罗佛比我觉得有点讽刺
    • 型@ICC97你是对的-意思很清楚。无意中讽刺,向作者道歉。
    • 型世界需要的是另一条证实讽刺的评论线,但如果仔细阅读,我已经写了,但这应该清楚。


    博士

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    {-# LANGUAGE InstanceSigs #-}

    newtype Id t = Id t

    instance Monad Id where
       return :: t -> Id t
       return = Id

       (=<<) :: (a -> Id b) -> Id a -> Id b
       f =<< (Id x) = f x

    序言

    应用算子函数

    okay.ZZU1

    典型定义

    okay.

    1
    2
    3
    4
    ($) :: (a -> b) -> a -> b
    f $ x = f x

    infixr 0 $

    在哈斯克尔原始函数的应用中在$术语中定义的.组成

    okay.

    1
    2
    3
    4
    (.) :: (b -> c) -> (a -> b) -> (a -> c)
    f . g = \ x -> f $ g x

    infixr 9 .

    并满足forall f g h.

    okay.

    1
    2
    3
         f . id  =  f            :: c -> d   Right identity
         id . g  =  g            :: b -> c   Left identity
    (f . g) . h  =  f . (g . h)  :: a -> d   Associativity

    是结合性的,而id是它的权利和左侧的身份。

    okay.克莱斯利三部曲

    在编程中,函子型结构体是一个实例的模子型类。定义与实现有几种等效变量,每一种对Monad抽象的直觉略有不同。

    okay.

    一种函子是一种类型的结构体f,一种类型的* -> *的函子类型。

    okay.

    1
    2
    3
    4
    {-# LANGUAGE KindSignatures #-}

    class Functor (f :: * -> *) where
       map :: (a -> b) -> (f a -> f b)

    另外,跟踪静态执行型协议,函子类型类的实例必须服从代数函子法EDOCX1〕〔10〕

    okay.

    1
    2
           map id  =  id           :: f t -> f t   Identity
    map f . map g  =  map (f . g)  :: f a -> f c   Composition / short cut fusion

    函子计算有类型

    okay.

    1
    forall f t. Functor f => f t

    c的背景下,由r的结果组成。

    okay.

    一种莫纳迪克函数或克莱斯利箭头有这种类型

    okay.

    1
    forall m a b. Functor m => a -> m b

    Kleisi arrows are functions that take one regulation aand return a monadic computation m b.

    okay.

    莫纳德是克莱斯利三重埃多克斯1&16术语的典型定义。

    okay.

    1
    (m, return, (=<<))

    作为类型类别实现

    okay.

    1
    2
    3
    4
    5
    class Functor m => Monad m where
       return :: t -> m t
       (=<<)  :: (a -> m b) -> m a -> m b

    infixr 1 =<<

    克莱斯利人的身份是一种克莱斯利箭,它促进了在摩纳哥的环境中的EDOCX1价值。应用克莱斯利箭头a -> m b来计算m a

    okay.

    克莱斯利成分EDOCX1&23

    okay.

    1
    2
    3
    4
    (<=<) :: Monad m => (b -> m c) -> (a -> m b) -> (a -> m c)
    f <=< g = \ x -> f =<< g x

    infixr 1 <=<

    复合两个Kleisli箭头,应用左箭头得出右箭头的应用结果。

    okay.

    Monad种类的实例必须遵循Monad Laws,在Kleisli组成术语中最优雅的说明:EDOCX1&5)

    okay.

    1
    2
    3
       return <=< g  =  g                :: b -> m c   Left identity
       f <=< return  =  f                :: c -> m d   Right identity
    (f <=< g) <=< h  =  f <=< (g <=< h)  :: a -> m d   Associativity

    是结合性的,而EDOCX1[…]是它的权利和左侧的身份。

    okay.身份

    身份类型

    okay.

    1
    type Id t = t

    类型上的身份功能

    okay.

    1
    Id :: * -> *

    作为函子解释

    okay.

    1
    2
    3
    4
    5
    6
    7
    8
       return :: t -> Id t
    =      id :: t ->    t

        (=<<) :: (a -> Id b) -> Id a -> Id b
    =     ($) :: (a ->    b) ->    a ->    b

        (<=<) :: (b -> Id c) -> (a -> Id b) -> (a -> Id c)
    =     (.) :: (b ->    c) -> (a ->    b) -> (a ->    c)

    在Canonical Haskell,The identity monad is defined

    okay.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    newtype Id t = Id t

    instance Functor Id where
       map :: (a -> b) -> Id a -> Id b
       map f (Id x) = Id (f x)

    instance Monad Id where
       return :: t -> Id t
       return = Id

       (=<<) :: (a -> Id b) -> Id a -> Id b
       f =<< (Id x) = f x

    选项

    选择类型

    okay.

    1
    data Maybe t = Nothing | Just t

    计算可能不产生t的结果。选择货币

    okay.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    instance Functor Maybe where
       map :: (a -> b) -> (Maybe a -> Maybe b)
       map f (Just x) = Just (f x)
       map _ Nothing  = Nothing

    instance Monad Maybe where
       return :: t -> Maybe t
       return = Just

       (=<<) :: (a -> Maybe b) -> Maybe a -> Maybe b
       f =<< (Just x) = f x
       _ =<< Nothing  = Nothing

    只有当Maybe a产生结果时才适用。

    okay.

    1
    newtype Nat = Nat Int

    自然数可以被编码为这些积分大于或等于零。

    okay.

    1
    2
    3
    toNat :: Int -> Maybe Nat
    toNat i | i >= 0    = Just (Nat i)
            | otherwise = Nothing

    自然数未在亚牵引下关闭。

    okay.

    1
    2
    3
    4
    (-?) :: Nat -> Nat -> Maybe Nat
    (Nat n) -? (Nat m) = toNat (n - m)

    infixl 6 -?

    选项Monad covers a basic form of exception handling.

    okay.

    1
    (-? 20) <=< toNat :: Int -> Maybe Nat

    列表

    列表货币,在列表类型上

    okay.

    1
    2
    3
    data [] t = [] | t : [t]

    infixr 5 :

    及其加性单倍体运算"append"好的。

    1
    2
    3
    4
    5
    (++) :: [t] -> [t] -> [t]
    (x : xs) ++ ys = x : xs ++ ys
    []       ++ ys = ys

    infixr 5 ++

    对非线性计算[t]进行编码,得到结果t的自然量0, 1, ...。好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    instance Functor [] where
       map :: (a -> b) -> ([a] -> [b])
       map f (x : xs) = f x : map f xs
       map _ []       = []

    instance Monad [] where
       return :: t -> [t]
       return = (: [])

       (=<<) :: (a -> [b]) -> [a] -> [b]
       f =<< (x : xs) = f x ++ f =<< xs
       _ =<< []       = []

    扩展将++所有结果列表[b]从Kleisli箭头a -> [b]的应用程序f x连接到[a]的元素,连接到单个结果列表[b]。好的。

    设正整数n的适当除数为好的。

    1
    2
    3
    4
    5
    divisors :: Integral t => t -> [t]
    divisors n = filter (`divides` n) [2 .. n - 1]

    divides :: Integral t => t -> t -> Bool
    (`divides` n) = (== 0) . (n `rem`)

    然后好的。

    1
    forall n.  let f = f <=< divisors in f n  =  []

    在定义monad类型类时,haskell标准使用其flip(绑定操作符EDOCX1)(11),而不是扩展=<<。好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    class Applicative m => Monad m where
       (>>=) :: forall a b. m a -> (a -> m b) -> m b

       (>>) :: forall a b. m a -> m b -> m b
       m >> k = m >>= \ _ -> k
       {-# INLINE (>>) #-}

       return :: a -> m a
       return = pure

       fail :: String -> m a
       fail s = errorWithoutStackTrace s

    为了简单起见,此解释使用类型类层次结构好的。

    1
    2
    class              Functor f
    class Functor m => Monad m

    在haskell中,当前的标准层次结构是好的。

    1
    2
    3
    class                  Functor f
    class Functor p     => Applicative p
    class Applicative m => Monad m

    因为不仅每个单子都是一个函数,而且每个应用程序都是一个函数,每个单子也都是一个应用程序。好的。

    使用列表monad,命令式伪代码好的。

    1
    2
    3
    4
    5
    for a in (1, ..., 10)
       for b in (1, ..., 10)
          p <- a * b
          if even(p)
             yield p

    大致翻译为do块好的。

    1
    2
    3
    4
    5
    do a <- [1 .. 10]
       b <- [1 .. 10]
       let p = a * b
       guard (even p)
       return p

    等效单子理解好的。

    1
    [p | a <- [1 .. 10], b <- [1 .. 10], let p = a * b, even p]

    以及表达好的。

    1
    2
    3
    4
    5
    6
    7
    [1 .. 10] >>= (\ a ->
       [1 .. 10] >>= (\ b ->
          let p = a * b in
             guard (even p) >>
                return p
       )
    )

    对于嵌套的绑定表达式来说,do符号和monad理解是句法上的糖分。bind运算符用于一元结果的本地名称绑定。好的。

    1
    2
    let x = v in e  =  (\ x -> e) $ v    =  v & (\ x -> e)
      do r <- m; c  =  (\ r -> c) =<< m  =  m >>= (\ r -> c)

    哪里好的。

    1
    2
    3
    4
    (&) :: a -> (a -> b) -> b
    (&) = flip ($)

    infixl 0 &

    定义了保护功能好的。

    1
    2
    3
    guard :: Additive m => Bool -> m ()
    guard True  = return ()
    guard False = fail

    其中单位类型或"空元组"好的。

    1
    data () = ()

    支持选择和失败的加法单元可以通过使用类型类抽象出来。好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    class Monad m => Additive m where
       fail  :: m t
       (<|>) :: m t -> m t -> m t

    infixl 3 <|>

    instance Additive Maybe where
       fail = Nothing

       Nothing <|> m = m
       m       <|> _ = m

    instance Additive [] where
       fail = []
       (<|>) = (++)

    其中fail<|>形成单体forall k l m.。好的。

    1
    2
    3
         fail <|> l  =  l
         k <|> fail  =  k
    (k <|> l) <|> m  =  k <|> (l <|> m)

    fail是加性单体的吸收/湮灭零元素。好的。

    1
    _ =<< fail  =  fail

    如果在好的。

    1
    guard (even p) >> return p

    even p为真,则防护产生[()],根据>>的定义,局部常数函数好的。

    1
    \ _ -> return p

    应用于结果()。如果为假,则警卫会生成Monad的fail[],这不会导致将kleisli箭头应用于>>。好的。状态

    众所周知,monad用于对有状态计算进行编码。好的。

    状态处理器是一个函数好的。

    1
    forall st t. st -> (t, st)

    这将使状态st转换,并产生结果t。国家可以是任何东西。无,标记,计数,数组,句柄,机器,世界。好的。

    状态处理器的类型通常称为好的。

    1
    type State st t = st -> (t, st)

    状态处理器monad是同类的* -> *函数或State st函数。状态处理器monad的kleisli箭头是函数好的。

    1
    forall st a b. a -> (State st) b

    在canonical haskell中,定义了状态处理器monad的懒惰版本。好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    newtype State st t = State { stateProc :: st -> (t, st) }

    instance Functor (State st) where
       map :: (a -> b) -> ((State st) a -> (State st) b)
       map f (State p) = State $ \ s0 -> let (x, s1) = p s0
                                         in  (f x, s1)

    instance Monad (State st) where
       return :: t -> (State st) t
       return x = State $ \ s -> (x, s)

       (=<<) :: (a -> (State st) b) -> (State st) a -> (State st) b
       f =<< (State p) = State $ \ s0 -> let (x, s1) = p s0
                                         in  stateProc (f x) s1

    状态处理器通过提供初始状态来运行:好的。

    1
    2
    3
    4
    5
    6
    7
    8
    run :: State st t -> st -> (t, st)
    run = stateProc

    eval :: State st t -> st -> t
    eval = fst . run

    exec :: State st t -> st -> st
    exec = snd . run

    状态访问由原语getput提供,状态单子上的抽象方法:好的。

    1
    2
    3
    4
    5
    {-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies #-}

    class Monad m => Stateful m st |&nbsp;m -> st where
       get :: m st
       put :: st -> m ()

    m -> st建议功能的状态依赖型st在单子mState t认为,例如,将确定的状态是t独特型。

    1
    2
    3
    4
    5
    6
    instance Stateful (State st) st where
       get :: State st st
       get = State $ \ s -> (s, s)

       put :: st -> State st ()
       put s = State $ \ _ -> ((), s)

    与使用单位对在C型analogously void

    1
    2
    3
    4
    5
    6
    7
    8
    9
    modify :: Stateful m st => (st -> st) -> m ()
    modify f = do
       s <- get
       put (f s)

    gets :: Stateful m st => (st -> t) -> m t
    gets f = do
       s <- get
       return (f s)

    经常使用的gets是一场accessors与记录。

    等效可变状态的线程单子

    1
    2
    3
    4
    5
    let s0 = 34
        s1 = (+ 1) s0
        n = (* 12) s1
        s2 = (+ 7) s1
    in  (show n, s2)

    s0 :: Int同样可靠,是透明的,但更多的优雅和实用infinitely

    1
    2
    3
    4
    5
    6
    7
    (flip run) 34
       (do
          modify (+ 1)
          n <- gets (* 12)
          modify (+ 7)
          return (show n)
       )

    modify (+ 1)是计算State Int ()型,除了其对return ()效应等效。

    1
    2
    3
    4
    5
    6
    7
    (flip run) 34
       (modify (+ 1) >>
          gets (* 12) >>= (\ n ->
             modify (+ 7) >>
                return (show n)
          )
       )

    该法associativity单子可以写在条款的forall m f g.>>=

    1
    (m >>= f) >>= g  =  m >>= (\ x -> f x >>= g)

    1
    2
    3
    4
    5
    6
    7
    do {                 do {                   do {
       r1 <- do {           x <- m;                r0 <- m;
          r0 <- m;   =      do {            =      r1 <- f r0;
          f r0                 r1 <- f x;          g r1
       };                      g r1             }
       g r1                 }
    }                    }

    中的表达状导向编程(如锈),最后一块是其产量的声明。该绑定是有时称为"可编程逻辑算子的分号"。

    primitives迭代控制结构是从一emulated monadically结构化编程

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    for :: Monad m => (a -> m b) -> [a] -> m ()
    for f = foldr ((>>) . f) (return ())

    while :: Monad m => m Bool -> m t -> m ()
    while c m = do
       b <- c
       if b then m >> while c m
            else return ()

    forever :: Monad m => m t
    forever m = m >> forever m

    输入/输出

    1
    data World

    《世界的状态处理器的I / O是一个和解的Haskell单子纯粹和真实的世界,一denotative和操作语义的功能。封闭当前模拟的严格执行:

    1
    type IO t = World -> (t, World)

    的相互作用是通过提高primitives不纯

    1
    2
    3
    4
    5
    6
    7
    getChar         :: IO Char
    putChar         :: Char -> IO ()
    readFile        :: FilePath -> IO String
    writeFile       :: FilePath -> String -> IO ()
    hSetBuffering   :: Handle -> BufferMode -> IO ()
    hTell           :: Handle -> IO Integer
    . . .              . . .

    该代码是用IO杂质是一个永久的primitives按照治疗方案的类型系统。因为性是可怕的,什么发生在IO,stays IO中。

    1
    unsafePerformIO :: IO t -> t

    或者,至少,应该的。

    Haskell程序的类型的签名)。

    1
    2
    main :: IO ()
    main = putStrLn"Hello, World!"

    扩大到

    1
    World -> ((), World)

    该功能是变换的世界。

    epilogue

    在Haskell的类型和对象类是送来的Haskell函数之间的态射是送来的类型是"快速和宽松"的范畴,Hask

    t函子的范畴是从C映射到类中的每个对象的CD;对象在一D

    1
    2
    Tobj :  Obj(C) -> Obj(D)
       f :: *      -> *

    在与每个在其morphism Cmorphism D

    1
    2
    Tmor :  HomC(X, Y) -> HomD(Tobj(X), Tobj(Y))
     map :: (a -> b)   -> (f a -> f b)

    XYC对象中。HomC(X, Y)是同态的态射类中的所有X -> YC。的身份和morphism函子保持必须的成分,"结构"中的CD

    1
    2
    3
    4
                        Tmor    Tobj

          T(id)  =  id        : T(X) -> T(X)   Identity
    T(f) . T(g)  =  T(f . g)  : T(X) -> T(Z)   Composition

    本研究kleisli类类C给出由三kleisli

    1
    <T, eta, _*>

    一endofunctor

    1
    T : C -> C

    (一fmorphism eta(身份),return),和一个扩展*(=<<)算子。

    在每个kleisli morphism Hask

    1
    2
          f :  X -> T(Y)
          f :: a -> m b

    由扩展算子

    1
    2
       (_)* :  Hom(X, T(Y)) -> Hom(T(X), T(Y))
      (=<<) :: (a -> m b)   -> (m a -> m b)

    给出了该类中的kleisli morphism Hask

    1
    2
         f* :  T(X) -> T(Y)
    (f =<<) :: m a  -> m b

    一类组合在kleisli .T给出的扩展条款

    1
    2
     f .T g  =  f* . g       :  X -> T(Z)
    f <=< g  =  (f =<<) . g  :: a -> m c

    在axioms和satisfies类

    1
    2
    3
    4
    5
    6
    7
    8
           eta .T g  =  g                :  Y -> T(Z)   Left identity
       return <=< g  =  g                :: b -> m c

           f .T eta  =  f                :  Z -> T(U)   Right identity
       f <=< return  =  f                :: c -> m d

      (f .T g) .T h  =  f .T (g .T h)    :  X -> T(U)   Associativity
    (f <=< g) <=< h  =  f <=< (g <=< h)  :: a -> m d

    其中,应用等价转换好的。

    1
    2
    3
    4
    5
    6
    7
    8
    9
         eta .T g  =  g
         eta* . g  =  g               By definition of .T
         eta* . g  =  id . g          forall f.  id . f  =  f
             eta*  =  id              forall f g h.  f . h  =  g . h  ==>  f  =  g

    (f .T g) .T h  =  f .T (g .T h)
    (f* . g)* . h  =  f* . (g* . h)   By definition of .T
    (f* . g)* . h  =  f* . g* . h     . is associative
        (f* . g)*  =  f* . g*         forall f g h.  f . h  =  g . h  ==>  f  =  g

    在扩展方面,是规范的给予。好的。

    1
    2
    3
    4
    5
    6
    7
    8
                   eta*  =  id                 :  T(X) -> T(X)   Left identity
           (return =<<)  =  id                 :: m t -> m t

               f* . eta  =  f                  :  Z -> T(U)      Right identity
       (f =<<) . return  =  f                  :: c -> m d

              (f* . g)*  =  f* . g*            :  T(X) -> T(Z)   Associativity
    (((f =<<) . g) =<<)  =  (f =<<) . (g =<<)  :: m a -> m c

    monads也可以定义为自然转换mu,而不是kleislian扩展,在名为join的编程中。单胞菌在mu的术语中被定义为内函数类C的三倍。好的。

    1
    2
         T :  C -> C
         f :: * -> *

    两种自然变形好的。

    1
    2
    3
    4
    5
       eta :  Id -> T
    return :: t  -> f t

        mu :  T . T   -> T
      join :: f (f t) -> f t

    满足同等条件好的。

    1
    2
    3
    4
    5
           mu . T(mu)  =  mu . mu               :  T . T . T -> T . T   Associativity
      join . map join  =  join . join           :: f (f (f t)) -> f t

          mu . T(eta)  =  mu . eta       =  id  :  T -> T               Identity
    join . map return  =  join . return  =  id  :: f t -> f t

    然后定义monad类型类好的。

    1
    2
    3
    class Functor m => Monad m where
       return :: t -> m t
       join   :: m (m t) -> m t

    期权Monad的规范mu实施:好的。

    1
    2
    3
    4
    5
    instance Monad Maybe where
       return = Just

       join (Just m) = m
       join Nothing  = Nothing

    concat函数好的。

    1
    2
    3
    concat :: [[a]] -> [a]
    concat (x : xs) = x ++ concat xs
    concat []       = []

    是单子列表中的join。好的。

    1
    2
    3
    4
    5
    6
    instance Monad [] where
       return :: t -> [t]
       return = (: [])

       (=<<) :: (a -> [b]) -> ([a] -> [b])
       (f =<<) = concat . map f

    join的实现可以使用等价物从扩展形式转换而来。好的。

    1
    2
         mu  =  id*           :  T . T -> T
       join  =  (id =<<)      :: m (m t) -> m t

    mu到扩展形式的反向转换由下式给出:好的。

    1
    2
         f*  =  mu . T(f)     :  T(X) -> T(Y)
    (f =<<)  =  join . map f  :: m a -> m b
    • PhilipWadler:函数编程的Monads好的。

    • Simon L Peyton Jones,Philip Wadler:命令式函数编程好的。

    • Jonathan M.D.Hill,Keith Clarke:分类理论导论,分类理论单子及其与函数编程的关系’好的。

    • 克莱斯利分类好的。

    • 优生学:计算和单子的概念好的。

    • 单子不是什么好的。

    But why should a theory so abstract be of any use for programming?

    Ok.

    The answer is simple: as computer scientists, we value abstraction! When we design the interface to a software component, we want it to reveal as little as possible about the implementation. We want to be able to replace the implementation with many alternatives, many other ‘instances’ of the same ‘concept’. When we design a generic interface to many program libraries, it is even more important that the interface we choose have a variety of implementations. It is the generality of the monad concept which we value so highly, it is because category theory is so abstract that its concepts are so useful for programming.

    Ok.

    It is hardly suprising, then, that the generalisation of monads that we present below also has a close connection to category theory. But we stress that our purpose is very practical: it is not to ‘implement category theory’, it is to find a more general way to structure combinator libraries. It is simply our good fortune that mathematicians have already done much of the work for us!

    Ok.

    从将单子泛化到约翰·休斯的《箭》。好的。好啊。


    monad是用来封装具有变化状态的对象的东西。它通常出现在不允许您具有可修改状态的语言中(例如,haskell)。

    文件I/O就是一个例子。

    您可以使用monad作为文件I/O,将不断变化的状态性质隔离为只使用monad的代码。monad中的代码可以有效地忽略monad之外世界的变化状态——这使得理解程序的整体效果变得容易得多。

    • 据我所知,单子比这更重要。在"纯"函数语言中封装可变状态只是monads的一个应用。


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

    编辑:这个系列-用javascript解释Monads-是一个"平衡"我。

      百万千克1

      http://blog.jcoglan.com/2011/03/05/translation-from-haskell-to-javascript-of-selected-portions-of-the-best-introduction-to-monads-ive-ever-read(从-haskell-to-javascript-of-selected-portions-of-the-best-introdu/

      百万千克1百万千克1

      http://blog.jcoglan.com/2011/03/06/monad-syntax-for-javascript/

      百万千克1百万千克1

      http://blog.jcoglan.com/2011/03/11/promises-are-the-monad-of-asynchronous-programming/

      百万千克1

    我认为理解单子会让你毛骨悚然。从这个意义上说,尽可能多地阅读"教程"是一个好主意,但通常奇怪的东西(不熟悉的语言或语法)会阻止你的大脑集中精力于基本的东西。

    有些事情我很难理解:

      百万千克1基于规则的解释对我来说根本不起作用,因为大多数实际示例实际上需要的不仅仅是返回/绑定。百万千克1百万千克1而且,称他们为规则也没有帮助。它更像是一个例子,"有些东西有共同点,让我们把它们叫做‘monads’,而共同点叫做‘rules’"。百万千克1百万千克1return(a -> M和bind(M -> (a -> M) -> M是很好的,但我不明白bind是如何从M中提取a以进入a -> M的。我不认为我在任何地方读过(可能对其他人来说很明显),返回的反转(M -> a必须存在于monad中,它不需要被暴露。百万千克1

    • 你似乎是唯一一个解决了我理解单子的主要问题的人。从来没有人谈论过如何提取价值。它是否依赖于实现?
    • @葡萄酒,谢谢你的评论。恐怕自从我写下这个答案以来,我的理解没有进步太多,所以我不能增加太多。价值提取在逻辑上必须存在,所以也许这就是为什么没人费心提及它的原因。
    • 我有一个讨论,将monad视为一个类型扩展,其中原始类型b转换为扩展类型m,关联的运算符包装为now service m。这些包装器将处理monad的特性。特别是,从扩展类型中提取原始类型并将其传递给它的包装运算符,然后提升结果。monad的好处是可以保留简单的声明性表达式。在我的治疗中,我讨论了扩展数字类型系统以包括divbyzero值以避免需要检查
    • @是的,它依赖于实现。编写函数>>=的人可以访问monad的内部。例如,请参阅maybe monad并查找instance Monad Maybe。你会看到,当左手边是Just x时,我们返回k x。模式匹配为您打开包装。在每个Monad实现中都会发生类似的事情。


    解释monad似乎就像解释控制流语句。假设一个非程序员要求你解释它们?

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

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

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

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

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

    • 透镜,而不是单子,是功能性程序员使用命令式代码而实际上不承认它的一种方式。


    我也在试着理解单子。这是我的版本:

    单子是关于重复性事物的抽象。首先,monad本身是一个类型化接口(类似于抽象的泛型类),它有两个函数:具有定义签名的bind和return。然后,我们可以基于抽象的monad创建具体的monad,当然还有bind和return的具体实现。此外,bind和return必须满足一些不变量,以便能够组成/链接具体的monad。

    当我们有接口、类型、类和其他工具来创建抽象时,为什么要创建monad概念?因为monads提供了更多:它们以一种方式强制重新思考问题,使得可以在不使用任何样板文件的情况下合成数据。


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

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

    • 型课程已经不在Coursera上了
    • 型这是对Coursera的一个令人惊讶的和糟糕的反思。


    一个非常简单的答案是:

    monad是一种抽象,它为封装值、计算新的封装值和解包封装值提供了接口。

    在实践中,它们的便利之处在于,它们提供了一个统一的接口,用于创建建模状态而非状态的数据类型。

    重要的是要理解monad是一种抽象,即处理某种数据结构的抽象接口。然后,该接口用于构建具有单态行为的数据类型。

    您可以在Ruby的Monads中找到一个非常好和实用的介绍,第1部分:介绍。


    实际上,monad允许回调嵌套(具有相互递归的线程状态(请注意连字符)(以可组合(或可分解)的方式)(具有类型安全性(有时(取决于语言))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))

    例如,这不是单子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //JavaScript is 'Practical'
    var getAllThree =
             bind(getFirst, function(first){  
      return bind(getSecond,function(second){  
      return bind(getThird, function(third){  
        var fancyResult = // And now make do fancy
                          // with first, second,
                          // and third
        return RETURN(fancyResult);
      });});});

    但是monads支持这种代码。monad实际上是一组类型,用于:{bind,RETURN,maybe others I don't know...}。这在本质上是不必要的,实际上是不切实际的。

    现在我可以用它了:

    1
    2
    3
    4
    5
    6
    7
    var fancyResultReferenceOutsideOfMonad =  
      getAllThree(someKindOfInputAcceptableToOurGetFunctionsButProbablyAString);  

    //Ignore this please, throwing away types, yay JavaScript:
    //  RETURN = K
    //  bind = \getterFn,cb ->
    //    \in -> let(result,newState) = getterFn(in) in cb(result)(newState)

    或者打破它:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    var getFirstTwo =
               bind(getFirst, function(first){  
        return bind(getSecond,function(second){  
          var fancyResult2 = // And now make do fancy
                             // with first and second
          return RETURN(fancyResult2);
        });})
      , getAllThree =
               bind(getFirstTwo, function(fancyResult2){  
        return bind(getThird,    function(third){  
          var fancyResult3 = // And now make do fancy
                             // with fancyResult2,
                             // and third
          return RETURN(fancyResult3);
        });});

    或忽略某些结果:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    var getFirstTwo =
               bind(getFirst, function(first){  
        return bind(getSecond,function(second){  
          var fancyResult2 = // And now make do fancy
                             // with first and second
          return RETURN(fancyResult2);
        });})
      , getAllThree =
               bind(getFirstTwo, function(____dontCare____NotGonnaUse____){  
        return bind(getThird,    function(three){  
          var fancyResult3 = // And now make do fancy
                             // with `three` only!
          return RETURN(fancyResult3);
        });});

    或者简化一个简单的案例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    var getFirstTwo =
               bind(getFirst, function(first){  
        return bind(getSecond,function(second){  
          var fancyResult2 = // And now make do fancy
                             // with first and second
          return RETURN(fancyResult2);
        });})
      , getAllThree =
               bind(getFirstTwo, function(_){  
        return bind(getThird,    function(three){  
          return RETURN(three);
        });});

    至(使用"正确身份"):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    var getFirstTwo =
               bind(getFirst, function(first){  
        return bind(getSecond,function(second){  
          var fancyResult2 = // And now make do fancy
                             // with first and second
          return RETURN(fancyResult2);
        });})
      , getAllThree =
               bind(getFirstTwo, function(_){  
        return getThird;
        });

    或者把它们塞回一起:

    1
    2
    3
    4
    5
    var getAllThree =
               bind(getFirst, function(first_dontCareNow){  
        return bind(getSecond,function(second_dontCareNow){  
        return getThird;
        });});

    这些能力的实用性并没有真正显现出来,或者在你试图解决真正棘手的问题之前保持清醒比如解析,或者模块/Ajax/资源加载。

    你能想象成千上万行的indexof/substring逻辑吗?如果频繁的解析步骤包含在小函数中怎么办?像charsspacesupperCharsdigits这样的功能?如果这些函数在回调中给出了结果,不必与regex组和arguments.slice混淆?如果他们的组成/分解被很好地理解呢?这样你就可以自下而上构建大型解析器了?

    所以管理嵌套回调范围的能力是非常实用的,尤其是在使用单语法分析器组合器库时。(也就是说,根据我的经验)

    不要挂断电话:-范畴论- Maybe单子-单子定律-哈斯克尔-!!!!!


    下面是一些"{| a |m}"代表一片monadic日期。一个日期型advertises a

    1
    2
    3
            (I got an a!)
              /        
        {| a |m}

    函数,f,知道如何创建一个单子,如果只有一个:它有a

    1
    2
    3
    4
    5
    6
    7
    8
    9
           (Hi f! What should I be?)
                          /
    (You?. Oh, you'll be /
     that data there.)  /
     /                 /  (I got a b.)
    |    --------------      |
    |  /                     |
    f a                      |
      |--later->       {| b |m}

    在这里,我们看到的功能,探讨其tries f,但有rebuked单子。

    1
    2
    3
    4
    5
    (Hmm, how do I get that a?)
     o       (Get lost buddy.
    o         Wrong type.)
    o       /
    f {| a |m}

    f发现功能的方式,利用>>=a提取物。

    1
    2
    3
    4
    5
    6
    7
            (Muaahaha. How you
             like me now!?)      
        (Better.)      \
            |     (Give me that a.)
    (Fine, well ok.)    |
             \          |
       {| a |m}   >>=   f

    不知道f小的单子,是在与>>=合谋。

    1
    2
    3
    4
    5
    6
    7
    8
                (Yah got an a for me?)      
    (Yeah, but hey    |
     listen. I got    |
     something to     |
     tell you first   |
     ...)   \        /
             |      /
       {| a |m}   >>=   f

    但事实上,他们谈论的是什么?嗯,这取决于上的单子。会说话的蛛网膜下腔出血(SAH)有限公司使用完全抽象的;你必须体验到一些特别的monads -PSP与身上的理解。

    例如,可能的数据类型

    1
     data Maybe a = Nothing | Just a

    有个单子样将以下行为的实例…………………

    其中,如果是Just a案例

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
                (Yah what is it?)      
    (... hm? Oh,      |
    forget about it.  |
    Hey a, yr up.)    |
                \     |
    (Evaluation  \    |
    time already? \   |
    Hows my hair?) |  |
          |       /   |
          |  (It's    |
          |  fine.)  /
          |   /     /    
       {| a |m}   >>=   f

    但情况Nothing

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
            (Yah what is it?)      
    (... There      |
    is no a. )      |
      |        (No a?)
    (No a.)         |
      |        (Ok, I'll deal
      |         with this.)
       \            |
        \      (Hey f, get lost.)
         \          |   ( Where's my a?
          \         |     I evaluate a)
           \    (Not any more  |
            \    you don't.    |
             |   We're returning
             |   Nothing.)   /
             |      |       /
             |      |      /
             |      |     /
       {| a |m}   >>=   f      (I got a b.)
                        |  (This is   \
                        |   such a     \
                        |   sham.) o o  \
                        |               o|
                        |--later-> {| b |m}

    也许是我们的计算,如果它继续单子,其实是包含在aaborts advertises,但如果它不是在计算的结果,但是,仍然是一片monadic日期,但不是f产出。也许是这个原因,单子是用来代表当前的失败。

    monads differently表现不同。与其他类型的数据列表是monadic实例。他们的行为如以下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    (Ok, here's your a. Well, its
     a bunch of them, actually.)
      |
      |    (Thanks, no problem. Ok
      |     f, here you go, an a.)
      |       |
      |       |        (Thank's. See
      |       |         you later.)
      |  (Whoa. Hold up f,      |
      |   I got another         |
      |   a for you.)           |
      |       |      (What? No, sorry.
      |       |       Can't do it. I
      |       |       have my hands full
      |       |       with all these"b"
      |       |       I just made.)
      |  (I'll hold those,      |
      |   you take this, and   /
      |   come back for more  /
      |   when you're done   /
      |   and we'll do it   /
      |   again.)          /
       \      |  ( Uhhh. All right.)
        \     |       /    
         \    \      /
    {| a |m}   >>=  f

    在这个案例的功能,知道如何使从它的输入列表,但不知道该怎么做额外的输入和额外的列表。该绑定是由f>>=,出的多输出相结合。这个例子表明,包括一对在>>=负责提取a,它也有可能访问到的产出f束缚。事实上,它将永远不会知道,除非它在任何a提取物可能具有相同的输出类型的上下文。

    有其他monads这是用来代表不同的情况。这是一些characterizations of等等更多。在IO单子是不是真的有一个a,但它知道的人,你将得到a。隐藏的秘密之State st单子有st通,这将对下ff凸轮表,虽然只是一a所要求的。在Reader rState st单子是同类的,虽然它fr只让看。

    这一点在所有的是,任何类型的数据,这是符合其本身是一declaring单子的一些排序)中提取价值从周围的单子。大增益从所有这一切?嗯,它的易用性有足够之沙发排序计算的上下文。它可以把凌乱的,然而,当本拉登在stringing多上下文的计算。在照顾解决单子业务上下文的相互作用(这样的程序员是不是必须的。

    注意,使用的>>=eases)由制造一些混乱的f自主的离开。这是的情况下,在上述的Nothing例如,f不再能决定该怎么做的情况下,在Nothing>>=;它的编码。这是权衡。如果它是必要的适当的f决定该怎么做的情况下,应该有一个fNothing,然后从Maybe aMaybe b函数)。在这个案例,是一Maybe单子被失去。

    注意,然而,有时在没有数据类型的构造函数(口*,看着你,如果你想),我们的工作与我们的小,但有价值的选择上与它的工作monadic接口。


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

    我从Monads上的funfunfunction视频中得到了这个基础,并且学习了haskell章节"更多的Monads"。我强烈推荐观看FunfunFunction视频。

    最简单的是,monad是具有mapflatMap功能的对象(haskell中的bind)。有一些额外的必需属性,但这些是核心属性。

    flatMap将映射的输出"展平",对于列表,这只是连接列表的值,例如。

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

    所以在python中,我们基本上可以用这两个函数实现monad:

    1
    2
    3
    4
    5
    def flatMap(func, lst):
        return concat(map(func, lst))

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

    func是接受一个值并返回一个列表的任何函数,例如

    1
    lambda x: [x*x]

    解释

    为了清晰起见,我通过一个简单的函数在python中创建了concat函数,它对列表进行了汇总,即[] + [1] + [4] + [9] = [1, 4, 9](haskell有一个本机concat方法)。

    我假设您知道map函数是什么,例如:

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

    展平是monad的关键概念,对于每个monad对象,这种展平允许您获得包装在monad中的值。

    现在我们可以打电话给:

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

    此lambda正在获取值x并将其放入列表中。monad可以处理从值到monad类型的任何函数,在本例中是一个列表。

    这就是你的单子定义。

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

    更多解释

    其他没有列出的例子是javascript承诺,它有then方法和javascript流,它们有flatMap方法。

    所以promises和streams使用了一个稍微不同的函数,它将流或promises展平并从内部返回值。

    haskell列表monad具有以下定义:

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

    即:return有三个函数(在大多数其他语言中不与返回混淆)、>>=fail

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

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

    还有:

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

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

    这是你要找的视频。

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


    Monad是一个Applicative(即可以提升二进制的东西,因此,"any ary"—函数到(1)并向(2)中注入纯值)Functor(即可以映射的东西,(3)或提升一元函数到(3)),增加了扁平嵌套数据类型的能力。在Haskell中,这种扁平化操作称为join

    对于列表,特定类型为:

    1
    join  ::  [[a]] -> [a]

    适用于任何monad的一般类型是:

    1
    join  ::  Monad m  =>  m (m a) -> m a

    bind(>>=操作符)只是fmapjoin的组合,即(m >>= f) = join (fmap f m)

    对于许多单子,do符号do { x <- m; return x }和bind运算符m >>= (\x -> return x)都可以读作

    first"do" m, and when it's done, get its"result" as x and let me use it to"do" something else.

    (1)与liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c一起

    (2)与pure :: Applicative f => a -> f a一起

    (3)带fmap :: Functor f => (a -> b) -> f a -> f b

    还有等效的单子方法,

    1
    2
    3
    liftM2 :: Monad f => (a -> b -> c) -> f a -> f b -> f c
    return :: Monad f => a -> f a
    liftM  :: Monad f => (a -> b) -> f a -> f b

    数学思维

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

    return data:创建一个只在monad世界中生成数据的计算。

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

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

    任何单元计算都将在且仅当主函数触发时进行计算。

    • 型大错误:单元计算可以触发wo。主。


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

    1
    a -> b

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

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

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

    1
    a -> f b

    是从as到bs的计算类型,这些计算是进入fs的。您还可以组合它们:

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

    事实证明,这种模式在任何地方都会出现,并且与上面的第一个组合(关联性、右和左标识)具有相同的属性。

    我们必须给这个模式起一个名字,但是如果知道第一个构图的形式特征是一个半群体,这会有帮助吗?

    "单子和括号一样有趣和重要"(奥列格基塞利约夫)


    monad是一个带有特殊机器的盒子,它允许你用两个嵌套的盒子制作一个普通的盒子,但是仍然保留了两个盒子的一些形状。好的。

    具体来说,它允许您执行Monad m => m (m a) -> m a类型的join。好的。

    它还需要一个return操作,它只是包装一个值。return :: Monad m => a -> m a你也可以说join拆箱和return包装——但是join不是Monad m => m a -> a型(它不拆开所有的单体,而是拆开里面有单体的单体。)好的。

    因此,它需要一个单体盒子(Monad m =>m,里面有一个盒子((m a)),并制作一个普通的盒子(m a)。好的。

    然而,通常用monad来表示(>>=)(口语"bind")运算符,它基本上只是fmapjoin相继出现。具体地说,好的。

    1
    2
    x >>= f = join (fmap f x)
    (>>=) :: Monad m => (a -> m b) -> m a -> m b

    注意,该函数出现在第二个参数中,与fmap相反。好的。

    还有,join = (>>= id)。好的。

    为什么这样有用?本质上,它允许您在某种框架(monad)中工作时,生成将动作串在一起的程序。好的。

    单胞菌在哈斯克尔最显著的用途是单胞菌IO。现在,IO是在haskell中对动作进行分类的类型。在这里,monad系统是唯一的保存方法(大花哨的词):好的。

    • 参考透明度
    • 懒惰
    • 纯度

    本质上,一个IO操作(如getLine :: IO String)不能用字符串替换,因为它总是有不同的类型。把IO想象成一个神奇的盒子,它能把东西传送给你。但是,仍然只是说getLine :: IO String和所有函数接受IO a会导致混乱,因为可能不需要这些函数。const"üp§" getLine会做什么?(const放弃了第二个论点。const a b = a不需要评估getLine,但应该做IO!这使得行为变得相当不可预测——也使得类型系统不那么"纯粹",因为所有函数都采用aIO a值。好的。

    输入IOmonad。好的。

    要将操作串在一起,只需扁平嵌套的操作。为了将函数应用于IO操作的输出,在IO a类型中的a只需使用(>>=)即可。好的。

    例如,输出输入行(输出行是产生IO动作的函数,与EDOCX1的右参数(33)匹配):好的。

    1
    2
    getLine >>= putStrLn :: IO ()
    -- putStrLn :: String -> IO ()

    这可以用do环境更直观地写:好的。

    1
    2
    do line <- getLine
       putStrLn line

    本质上,一个do区块如下:好的。

    1
    2
    3
    4
    5
    6
    7
    do x <- a
       y <- b
       z <- f x y
       w <- g z
       h x
       k <- h z
       l k w

    …转化为:好的。

    1
    2
    3
    4
    5
    6
    7
    a     >>= \x ->
    b     >>= \y ->
    f x y >>= \z ->
    g z   >>= \w ->
    h x   >>= \_ ->
    h z   >>= \k ->
    l k w

    还有m >>= \_ -> f>>操作符(当框中的值不需要在框中生成新框时)也可以写a >> b = a >>= const b(const a b = a)好的。型

    另外,return运算符是根据IO直观性建模的-它返回一个具有最小上下文的值,在这种情况下没有IO。由于IO a中的a表示返回的类型,这与命令式编程语言中的return(a)类似,但它不会阻止动作链!f >>= return >>= gf >>= g相同。只有当您返回的术语在链的早期创建时,它才有用——见上文。好的。型

    当然,还有其他的单子,否则它不会被称为单子,它会被称为"IO控制"之类的东西。好的。型

    例如,列表monad(Monad []通过连接变平—使(>>=)运算符对列表的所有元素执行函数。这可以看作是"不确定性",其中列表是许多可能的值,monad框架正在进行所有可能的组合。好的。型

    例如(在GHCI中):好的。型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    Prelude> [1, 2, 3] >>= replicate 3  -- Simple binding
    [1, 1, 1, 2, 2, 2, 3, 3, 3]
    Prelude> concat (map (replicate 3) [1, 2, 3])  -- Same operation, more explicit
    [1, 1, 1, 2, 2, 2, 3, 3, 3]
    Prelude> [1, 2, 3] >>"uq"
    "uququq"
    Prelude> return 2 :: [Int]
    [2]
    Prelude> join [[1, 2], [3, 4]]
    [1, 2, 3, 4]

    因为:好的。型

    1
    2
    3
    join a = concat a
    a >>= f = join (fmap f a)
    return a = [a]  -- or"= (:[])"

    "也许单子"只会使Nothing的所有结果无效。也就是说,绑定自动检查函数(a >>=f是否返回或值(a>>= f是否为Nothing—然后返回Nothing。好的。型

    1
    2
    3
    4
    join       Nothing  = Nothing
    join (Just Nothing) = Nothing
    join (Just x)       = x
    a >>= f             = join (fmap f a)

    或者更明确地说:好的。型

    1
    2
    Nothing  >>= _      = Nothing
    (Just x) >>= f      = f x

    状态monad用于修改某些共享状态的函数s -> (a, s),因此>>=的参数是:: a -> s -> (a, s)。这个名称有点用词不当,因为State实际上是用于状态修改函数,而不是用于状态—状态本身确实没有有趣的属性,它只是被更改了。好的。型

    例如:好的。型

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    pop ::       [a] -> (a , [a])
    pop (h:t) = (h, t)
    sPop = state pop   -- The module for State exports no State constructor,
                       -- only a state function

    push :: a -> [a] -> ((), [a])
    push x l  = ((), x : l)
    sPush = state push

    swap = do a <- sPop
              b <- sPop
              sPush a
              sPush b

    get2 = do a <- sPop
              b <- sPop
              return (a, b)

    getswapped = do swap
                    get2

    然后:好的。型

    1
    2
    3
    4
    5
    6
    7
    8
    Main*> runState swap [1, 2, 3]
    ((), [2, 1, 3])
    Main*> runState get2 [1, 2, 3]
    ((1, 2), [1, 2, 3]
    Main*> runState (swap >> get2) [1, 2, 3]
    ((2, 1), [2, 1, 3])
    Main*> runState getswapped [1, 2, 3]
    ((2, 1), [2, 1, 3])

    也:好的。型

    1
    2
    Prelude> runState (return 0) 1
    (0, 1)

    。好啊。


    解释

    这是非常简单的,当用C/JAVA术语解释时:

  • monad是一个接受参数并返回特殊类型的函数。

  • 这个monad返回的特殊类型也称为monad。(单子是1和2的组合)

  • 有一些语法上的甜头可以使调用这个函数和类型转换更容易。

  • 例子

    monad对于使函数式编程人员的生活更简单很有用。典型的例子:Maybemonad有两个参数,一个值和一个函数。如果传递值为null,则返回null。否则,它将评估函数。如果我们需要一个特殊的返回类型,我们也会称这个返回类型为Maybe。一个非常粗略的实现方式如下:

    1
    2
    3
    4
    5
    6
    7
    object Maybe(object value, Func<object,object> function)
    {
        if(value==null)
            return null;

        return function(value);
    }

    这在C语言中是非常无用的,因为这种语言缺少使monad有用所需的语法糖。但是monad允许您用函数式编程语言编写更简洁的代码。

    程序员经常用链子来调用monad,就像这样:

    1
    var x = Maybe(x, x2 => Maybe(y, y2 => Add(x2, y2)));

    在本例中,只有当xy都是非null时,才会调用Add方法,否则将返回null

    回答

    回答最初的问题:monad是一个函数和一个类型。就像一个特殊的interface的实现。


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

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

    • f取一个字符串并产生另一个字符串(取前两个字母)
    • g接受一个字符串并生成另一个字符串(大写转换)

    因此,在任何语言中,"取前两个字母并将其转换为大写字母"的转换都将被写入g(f("some string")。所以,在纯完美函数的世界里,构图就是:先做一件事,然后再做另一件事。

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

    • f接受一个字符串并生成一个字符串或不生成任何字符串。
    • 只有在f没有失败的情况下,g才会生成字符串。否则,什么都不会产生

    所以现在,g(f("some string")需要一些额外的检查:"compute f,如果失败,那么g应该什么也不返回,否则compute g"

    这个想法可以应用于任何参数化类型,如下所示:

    让上下文[someType]是上下文中某个类型的计算。考虑功能

    • f:: AnyType -> Context[Sometype]
    • g:: Sometype -> Context[AnyOtherType]

    组合g(f())应读作"compute f.在此上下文中进行一些额外计算,然后在上下文中有意义的情况下计算g"