当前位置:  开发笔记 > 编程语言 > 正文

什么是monad?

如何解决《什么是monad?》经验,为你挑选了26个好方法。

最近简要介绍了Haskell,对于monad本质上是什么,简单,简洁,实用的解释是什么?

我发现我遇到的大多数解释都是相当难以接近的,缺乏实际细节.



1> JacquesB..:

第一:如果你不是数学家,monad这个词有点空洞.另一个术语是计算构建器,它更具描述性,它们实际上是有用的.

你问一些实际的例子:

示例1:列表理解:

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

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

事实证明,这实际上只是List monad中某些操作的语法糖.相同的列表理解可以写成:

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

甚至:

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

示例2:输入/输出:

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

两个示例都使用monad,AKA计算构建器.共同的主题是monad 以一些特定的,有用的方式运作.在列表推导中,操作被链接,使得如果操作返回列表,则对列表中的每个项执行以下操作.另一方面,IO monad按顺序执行操作,但传递"隐藏变量",表示"世界状态",这允许我们以纯函数方式编写I/O代码.

事实证明链接操作的模式非常有用,并且在Haskell中用于许多不同的事情.

另一个例子是例外:使用Errormonad,操作被链接,使得它们按顺序执行,除非抛出错误,在这种情况下,链的其余部分被放弃.

list-comprehension语法和do-notation都是使用>>=运算符链接操作的语法糖.monad基本上只是一种支持>>=运营商的类型.

示例3:解析器

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

parseExpr = parseString <|> parseNumber

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

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

这些操作char,digit等等都非常简单.它们匹配或不匹配.神奇的是管理控制流程的monad:操作按顺序执行,直到匹配失败,在这种情况下,monad回溯到最新状态<|>并尝试下一个选项.同样,一种使用一些额外的,有用的语义来链接操作的方法.

例4:异步编程

上面的例子在Haskell中,但事实证明F#也支持monad.这个例子是从Don Syme偷来的:

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- 它实际上等待单独线程上的响应,而主线程从函数返回.当收到响应时,最后三行在生成的线程上执行.

在大多数其他语言中,您必须为处理响应的行显式创建单独的函数.该async单子能够"分裂"自身块和推迟后半期的执行.(async {}语法表示块中的控制流由asyncmonad 定义.)

他们如何工作

那么monad怎么能做所有这些奇特的控制流程呢?在do-block(或在F#中调用的计算表达式)中实际发生的是每个操作(基本上每一行)都包含在一个单独的匿名函数中.然后使用bind运算符(>>=在Haskell中拼写)组合这些函数.由于bind操作组合了函数,它可以按照它认为合适的方式执行它们:顺序,多次,反向,丢弃一些,在感觉到它时在单独的线程上执行一些等等.

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

putStrLn "What is your name?"
>>= (\_ -> getLine)
>>= (\name -> putStrLn ("Welcome, " ++ name ++ "!"))

这是更加丑陋的,但实际发生的事情也更加明显.该>>=操作是神奇的成分:它需要一个值(左侧)和具有功能(右侧)相结合的,以产生新的价值.然后,下一个>>=运算符将获取该新值,并再次与函数组合以生成新值.>>=可以被视为一个迷你评估者.

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

最简单的实现>>=只是获取左边的值并将其应用于右边的函数并返回结果,但如前所述,使整个模式有用的是当monad的实现中有一些额外的事情发生时>>=.

值如何从一个操作传递到下一个操作还有一些额外的聪明,但这需要对Haskell类型系统进行更深入的解释.

加起来

在Haskell术语中,monad是一个参数化类型,它是Monad类型的一个实例,它>>=与一些其他运算符一起定义.在外行人看来,monad只是一种>>=定义操作的类型.

本身>>=只是链接函数的一种繁琐的方式,但是由于存在隐藏"管道"的记号,monadic操作变成了一个非常好的和有用的抽象,在语言的许多地方很有用,并且很有用用语言创建自己的迷你语言.

为什么单子难?

对于许多Haskell学习者来说,monad是他们像砖墙一样的障碍.并不是monad本身很复杂,而是实现依赖于许多其他高级Haskell功能,如参数化类型,类型类等.问题是Haskell I/O基于monad,I/O可能是你在学习新语言时想要理解的第一件事 - 毕竟,创建不生成任何语言的程序并不是很有趣输出.我没有立即解决这个鸡和蛋的问题,除非像对待"魔法发生在这里"之类的I/O,直到你有足够的语言其他部分经验.抱歉.

关于monad的优秀博客:http: //adit.io/posts/2013-04-17-functors,_applicatives,_and_monads_in_pictures.html


我读了所有这些并且仍然不知道monad是什么,除了Haskell程序员不能很好地解释这个事实.这些例子并没有多大帮助,因为这些都是没有monad可以做的事情,而这个答案并没有说清楚monad如何使它们变得更容易,只会更加混乱.这个答案中接近有用的部分是删除了例子#2的句法糖.我说接近,因为除了第一行之外,扩展与原始版本没有任何真正的相似之处.
解释monad似乎特有的另一个问题是它是用Haskell编写的.我不是说Haskell是一种糟糕的语言 - 我说这对于解释monad是一种糟糕的语言.如果我知道Haskell我已经理解了monad,那么如果你想解释monad,那么首先要使用一种不懂monad的人更容易理解的语言.如果您必须*使用Haskell,请不要使用语法糖 - 使用您可以使用的最小,最简单的语言子集,并且不要假设您了解Haskell IO.
另外,我不同意你关于为什么monad很难的结论.如果monad本身并不复杂,那么你应该能够在没有一堆行李的情况下解释它们是什么.当我问"什么是monad"这个问题时,我不想知道实现情况,我想知道它的意思是什么.到目前为止似乎答案是"因为haskell的作者是sadomasochists并决定你应该做一些愚蠢的事情来完成简单的事情,所以你必须学习使用haskell的monads,而不是因为它们在任何方面都有用他们自己"...
但是......那不可能是对的,可以吗?我认为单子很难,因为没有人能够弄清楚如何解释它们而不会陷入令人困惑的实施细节.我的意思是......什么是校车?它是一个金属平台,前面有一个装置,它消耗一种精炼的石油产品,在一个循环中驱动一些金属活塞,这些金属活塞又转动一个曲轴连接到一些驱动一些轮子的齿轮上.轮子周围有橡胶袋,与橡胶沥青表面相接,使座椅向前移动.座位前进是因为......
作为一个在理解monad时遇到很多问题的人,我可以说这个答案有点帮助.但是,还有一些我不理解的事情.列表理解以monad的形式出现在什么方面?那个例子有扩展形式吗?关于大多数monad解释的另一件事真的困扰我,包括这个 - 是他们继续混淆"什么是monad?" 与"什么是monad有益?" 和"monad如何实现?".当你写"Monad基本上只是一种支持>> =运算符的类型时,你跳过那条鲨鱼." 哪个让我...
@LaurenceGonsalves我同意查理在这里."Monad"主要指的是用于以纯粹功能的方式构造复杂行为的界面模式.Monads的存在更多是为了方便而不是必需.Maybe monad就是一个很好的例子.这不是必需的.您可以在代码中将if-then-else语句的长序列链接在一​​起,但是谁想要这样做呢?因此,不使用普通的函数组合运算符,而是使用'重载'函数组合运算符,它自动插入if-then-else的东西.这个运算符是>> =.
Monads来自范畴理论,数学家喜欢将其称为"一般抽象无意义".他们的意思是赞美.
抓我的头.这似乎是一个实现细节,它并没有真正帮助我回答"我为什么要使用monad"这个问题.这可能是真的,但直到那时的解释并没有为我做好准备.我并没有考虑"为什么这是一个棘手的问题的确,为什么,我会需要的是某种类型的支撑>> =运算符.嘿,原来这是一个单子是什么!" 不,这不是我的脑袋,因为我不知道什么是>>运算符,或者什么是有益的,我也没有遇到它解决的问题.
..."这个匿名函数*加入*那个*,*加入*另一个".另外,在代码中完全不同的地方,您可以定义"加入"的含义.在步行XML以查找Quantity属性的示例中,"join to"将被定义为"如果前一个匿名函数找到了它希望找到的XML节点,则继续执行下一个匿名函数.否则,停止评估所有匿名函数和报告错误." 所以...我认为这解释了......
完成了一个完全致力于函数式编程的荣誉级大学课程,并被我的讲师宣布为"高级Haskell程序员"......我仍然觉得我只是模糊地知道monads**是什么**.我认为问题在于monad是非常抽象的概念(这使得它们如此有用).我非常了解*个人*monad(list,IO,Maybe,Cont,ST等); 什么是难以捉摸的是这个叫monad**的东西是**以某种方式统一所有这些概念.
(事情只是"代码片段"),并表示这些事物是"联合在一起"的,而不必在任何地方重复"联合在一起"在具体案例中的含义.例如,假设您正在处理XML,并且需要到第三个Transaction元素,到第四个LineItem元素,并读取其Quantity属性.很简单.你甚至可以编写一个XPATH来实现它.但是,由于这些项目中的每一项都可能完全丢失,这使事情变得更加复杂.那么,是否还有第三个Transaction元素?如果doc只有2个Transaction元素怎么办...
...为了你.在这种情况下,Haskell可以解决问题.为什么?因为Haskell非常善于处理匿名函数.如果给Haskell一个匿名函数列表,它可以携带该列表并在以后执行这些函数,并使用不同的参数等.因此,Haskell可以允许您编写"路径行走"代码以获取Quantity属性*没有*乱丢它与错误检查代码.您可以将路径行走代码编写为匿名函数列表,并使用语法,...
@CharlieFlowers对不起,不,这没有帮助.你说monads使事情变得更容易"表达和维护"而没有任何证据支持这一点.相反,我看到很多人不理解它们,还有成堆无益的教程,这些教程表明即使是所谓的"开悟"也不能理解monad足以解释它们.你与DSL的比较也让我想知道你是在谈论的是do-notation而不是monad.在了解糖之前,我想了解实际的monad.
......还是根本没有?如果它有3个Transaction元素,但第3个Transaction元素没有第4个LineItem元素怎么办?最后,如果有第4个LineItem元素,但该元素没有Quantity属性,该怎么办?这会成为一种痛苦,因为你的代码将路径转移到你关心的Quantity属性,并且用一大堆错误检查代码来"处理",以处理文档中缺少所需项目的情况.Haskell希望成为一种强大的编程语言,解决这些棘手的表现力问题......
麻烦的是,你不能给出一个明确的具体例子.因为如果你尝试......它只是List,或者Maybe或IO.哪些都容易掌握,但不一定能帮助未开悟的人看到整个单子.我想这就是为什么他们很难; 它们确实足够简单(例如,你需要记住以正确实现和使用monad的数量远远小于你在Java中使用类所需要知道的数量).但monad的统一概念非常抽象.
布列塔尼和@musicfreak,他*解释了monad是什么以及它是如何工作的...但由于某种原因你无法遵循它.我想帮忙,但我已经知道太多了,因为我在学习曲线上有点(只是一点点)领先于你.如果我们能够共同努力找到差距,我很乐意帮助关闭它.如果您已经学会了答案,那么请告诉我.让我回答一个问题,这个问题可能是差距的一部分 - "为什么除了Haskell的其余部分我们还需要monad?" 我们需要monad因为我们希望能够写出一系列"东西"......
为什么monads存在于Haskell中,为什么你想要使用它们,以及它们如何工作的一部分(最重要的部分).如果你把它与JacquesB的优秀答案结合在一起,我们会到达某个地方吗?你的下一个问题是什么?
我完全同意@Breton.你给出了很好的例子,说明了什么类型的代码使用monad,但是你失败了,IMO,解释*monad是*和*它是如何工作的*,这是重要的.
当然,你稍后会补偿它,但我恐怕仍然不知道"什么是monad"这个问题的答案,更不用说简单的简洁解释了.
@LaurenceGonsalves我现在只有时间说一件事,但我希望它会有所帮助.你说你可以在没有monad的情况下做到这一切.是的,你可以.monad与dsl类似,因为你可以不用dsl.但它们很好,因为它们可以更容易地表达和维护您想要表达的内容.
@George你刚刚描述了`Maybe` monad.其他人做不同的事情.解释monad的问题在于,每一个都是非常明显的,但它们具有共同结构的事实起初可能是反直觉的.
伙计们,单子来自数学界。如果您想知道“什么是单子”,则必须与数学家交谈。仅仅因为它是某些编程语言,并不意味着程序员应该解释它的含义。例如,编程语言可以对数字求和,但是如果您想对*什么是*进行解释,则必须与数学家交谈。我希望现在已经清楚了。我的程序员同伴,不要再试图解释单子了。

2> Mathematical..:

解释"什么是monad"有点像说"什么是数字?" 我们一直使用数字.但想象你遇到了一个对数字一无所知的人.如何赫克你能解释的数字是什么?你怎么会开始描述为什么这可能有用呢?

什么是monad?简短的回答:这是一种将操作链接在一起的特定方式.

实质上,您正在编写执行步骤并使用"绑定功能"将它们链接在一起.(在Haskell中,它被命名>>=.)您可以自己编写对bind操作符的调用,或者您可以使用语法sugar,使编译器为您插入这些函数调用.但无论哪种方式,每个步骤都通过调用此绑定函数来分隔.

所以bind函数就像一个分号; 它分离了一个过程中的步骤.绑定功能的作用是获取上一步的输出,并将其输入下一步.

这听起来不太难,对吧?但是不止一种单子.为什么?怎么样?

好吧,绑定功能可以从一步获取结果,并将其提供给下一步.但是,如果这是"所有"monad所做的......那实际上并不是非常有用.这一点很重要:每个有用的 monad 除了成为monad 之外还会做其他事情.每一个有用的单子都有"特殊的力量",这使它独一无二.

(没有什么特别之处的monad 被称为"身份monad".与身份功能相似,这听起来像一个完全没有意义的事情,但结果却不是......但那是另一个故事™.)

基本上,每个monad都有自己的bind函数实现.您可以编写一个绑定函数,以便在执行步骤之间进行连接.例如:

如果每个步骤都返回一个成功/失败指示符,则只有在前一个步骤成功的情况下,才能让绑定执行下一步.这样,失败的步骤会"自动"中止整个序列,而不需要您进行任何条件测试.(The Failure Monad.)

扩展这个想法,你可以实现"例外".(错误MonadException Monad.)因为您自己定义它们而不是语言功能,所以您可以定义它们的工作方式.(例如,您可能希望忽略前两个异常,并且仅在抛出第三个异常时中止.)

您可以使每个步骤返回多个结果,并使绑定函数循环遍历它们,将每个步骤提供给下一步.这样,在处理多个结果时,您不必在整个地方继续编写循环.绑定功能"自动"为您完成所有这些.(名单Monad.)

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

您可以将其设置为可以替换"额外数据".这允许您模拟破坏性更新,而无需实际进行破坏性更新.(国家莫纳德及其堂兄作家莫纳德.)

因为您只是在模拟破坏性更新,所以您可以通过真正的破坏性更新轻松完成这些操作.例如,您可以撤消上次更新,或恢复为旧版本.

您可以创建一个可以暂停计算的monad ,这样您就可以暂停程序,进入并修改内部状态数据,然后恢复它.

您可以将"continuation"实现为monad.这可以让你打破人们的思想!

monad可以实现所有这些以及更多.当然,如果没有 monad ,所有这一切也是完全可能的.使用monads 非常容易.


来自非数学,非函数式编程背景,这个答案对我来说最有意义.
我很感激你的答案 - 特别是最后的让步,所有这一切当然也可能没有单子.有一点需要注意的是,使用monad它们*更容易*,但它通常不如没有它们那样有效.一旦您需要涉及变换器,函数调用(以及创建的函数对象)的额外分层具有难以查看和控制的成本,通过巧妙的语法呈现为不可见.
这是第一个真正让我知道monad是什么的答案.感谢您找到解释它的方法!
你的答案绝对精彩!万分感谢!

3> 小智..:

实际上,与莫纳德的共同理解相反,他们与国家无关.Monads只是一种包装东西的方法,并提供了对包裹的东西进行操作的方法,而无需展开它.

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

data Wrapped a = Wrap a

包装我们定义的东西

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

要在不解包的情况下执行操作,假设您有一个函数f :: a -> b,那么您可以执行此操作来解除该函数以对包装值执行操作:

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

这就是所有需要了解的内容.然而,事实证明,有一个更通用的功能来完成这个提升,这是bind:

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

bind可以做多一点fmap,但反之亦然.实际上,fmap只能用bind和来定义return.因此,在定义monad时...你给它的类型(这里是Wrapped a),然后说出它returnbind操作是如何工作的.

很酷的是,事实证明这是一种普遍的模式,它会在整个地方弹出,以纯粹的方式封装状态只是其中之一.

有关如何使用monad来引入函数依赖关系并因此控制评估顺序的好文章,就像它在Haskell的IO monad中使用一样,请查看IO Inside.

至于理解monad,不要太担心它.阅读他们您感兴趣的内容,如果您不理解,请不要担心.然后,只需要像Haskell这样的语言潜水就可以了.Monads是通过练习理解涓涓细流到你的大脑的其中一件事,有一天你突然意识到你理解它们.


@ mb21:如果您只是指出支架太多,请注意a-> b-> c实际上只是 - >(b-> c)的缩写.将这个特定的例子写成(a - > b) - >(Ta - > Tb)严格来说只是添加不必要的字符,但它在道德上是"正确的事情",因为它强调fmap映射类型a的函数 - > b到Ta - > Tb类型的函数.最初,这就是算子在类别理论中所做的事情,也就是monad来自哪里.

4> nlucaroni..:

但是,你可能已经发明了Monads!

sigfpe说:

但是所有这些都将monad引入了需要解释的深奥的东西.但我想说的是,它们根本不是深奥的.事实上,面对函数式编程中的各种问题,你不可避免地会遇到某些解决方案,所有解决方案都是monad的例子.事实上,如果你还没有,我希望你现在可以发明它们.然后,这是一个小步骤,注意到所有这些解决方案实际上都是伪装的相同解决方案.阅读本文之后,你可能会更好地理解monad上的其他文档,因为你会认识到你所看到的所有你已经发明的东西.

monad试图解决的许多问题都与副作用问题有关.所以我们将从他们开始.(注意monads让你做的不仅仅是处理副作用,特别是许多类型的容器对象可以被视为monad.对monad的一些介绍发现很难调和monad的这两种不同用法并且只关注一个或者另一个.)

在诸如C++这样的命令式编程语言中,函数的行为与数学函数完全不同.例如,假设我们有一个C++函数,它接受一个浮点参数并返回一个浮点结果.从表面上看,它似乎有点像数学函数映射实数到实数,但C++函数可以做的不仅仅是返回一个取决于其参数的数字.它可以读取和写入全局变量的值,也可以将输出写入屏幕并接收来自用户的输入.但是,在纯函数式语言中,函数只能读取其参数中提供给它的内容,并且它对世界产生影响的唯一方法是通过它返回的值.


[这个Sigfpe帖子的JavaScript翻译](https://blog.jcoglan.com/2011/03/05/translation-from-haskell-to-javascript-of-selected-portions-of-the-best-introduction-to -monads-ive-ever-read /)是学习monad的新方法,对于那些还没有学过高级Haskell的人来说!
...最好的方式不仅在互联网上,而且在任何地方.(Wadler的原始论文[Monads for functions programming](http://homepages.inf.ed.ac.uk/wadler/papers/marktoberdorf/baastad.pdf)我在下面的回答中提到的也很好.)没有数以万计的教程比较接近.

5> Chris Conway..:

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

在Haskell表示法中,编写了monad接口

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

这些操作都应该遵守一定的"规律",但是这不是很重要了:"法律"只是编纂操作的方式明智的实现应该表现(基本上,>>=return应该同意的价值观是如何被转化成单子实例和这>>=是关联的).

Monads不只是关于状态和I/O:它们抽象了一种常见的计算模式,包括使用状态,I/O,异常和非确定性.可能最容易理解的monad是列表和选项类型:

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

其中[]:是列表构造函数,++是连接运算符,Just并且NothingMaybe构造函数.这两个monad都在各自的数据类型上封装了常见且有用的计算模式(请注意,这两种模式都与副作用或I/O无关).

你真的必须编写一些非平凡的Haskell代码,以了解monad的含义以及它们有用的原因.


Monad不是数据类型。这是组成函数的规则:http://stackoverflow.com/a/37345315/1614973

6> Apocalisp..:

你应该先了解一个仿函数是什么.在此之前,了解更高阶的函数.

高阶函数是一个简单的函数,它的功能作为一个参数.

算符是任何类型的构造T为其中存在一个高阶函数,调用它map,该变换类型的函数a -> b(给定任何两种类型的ab)成一个函数T a -> T b.此map函数还必须遵守身份和组合的规律,以便以下表达式对所有人pq(Haskell表示法)都返回true :

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

例如,如果一个类型构造List函数配备了(a -> b) -> List a -> List b符合上述规则的类型函数,则它被称为仿函数.唯一实际的实施是显而易见的.结果List a -> List b函数遍历给定列表,(a -> b)为每个元素调用函数,并返回结果列表.

一个单子本质上只是一个仿函数T有两个额外的方法,join,类型T (T a) -> T a,和unit(有时被称为return,forkpure类型)a -> T a.对于Haskell中的列表:

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

为什么这有用?例如,因为您可以map在具有返回列表的函数的列表上.Join获取结果列表并将它们连接起来.List是monad,因为这是可能的.

你可以写,做了功能map,那么join.此函数被称为bind,或flatMap,或(>>=),或(=<<).这通常是在Haskell中给出monad实例的方式.

monad必须满足某些定律,即join必须是关联的.这意味着如果你有一个x类型的值,[[[a]]]那么join (join x)应该相等join (map join x).而且pure必须是一个身份join,使得join (pure x) == x.


根据该定义,加法是高阶函数.它需要一个数字并返回一个将该数字添加到另一个数字的函数.所以不,高阶函数是严格的函数,其域由函数组成.
稍微增加'更高阶函数'的def:它们可以采用OR RETURN函数.这就是为什么他们"更高",因为他们自己做事.

7> Aristotle Pa..:

[免责声明:我仍然试图完全修改monad.以下是我到目前为止所理解的内容.如果这是错的,希望知识渊博的人会叫我在地毯上.]

阿纳写道:

Monads只是一种包装东西的方法,并提供了对包裹的东西进行操作的方法,而无需展开它.

这正是它.这个想法是这样的:

    你需要一些价值并用一些额外的信息包装它.就像值是某种类型(例如,整数或字符串)一样,附加信息也是某种类型.

    例如,额外的信息可能是a Maybe或a IO.

    然后,您有一些运算符,允许您在携带附加信息的同时对包装数据进行操作.这些运算符使用附加信息来决定如何更改包装值上的操作行为.

    例如,a Maybe Int可以是a Just IntNothing.现在,如果你添加一个Maybe IntMaybe Int,运营商将检查,看看他们都是Just Int里面装的,如果是的话,那里展开IntS,通过他们的加法运算,形成的再包装Int到一个新的Just Int(这是一个有效Maybe Int),从而返回一个Maybe Int.但是如果其中一个是Nothing内部的,那么这个操作符将立即返回Nothing,这也是有效的Maybe Int.这样,您可以假装您的Maybe Ints只是正常数字并对它们进行常规数学运算.如果你得到一个Nothing,你的方程式仍将产生正确的结果 - 你无需在Nothing任何地方乱丢垃圾.

但这个例子正是发生了什么Maybe.如果额外信息是a IO,那么为IOs 定义的特殊运算符将被调用,并且在执行添加之前它可以做一些完全不同的事情.(好吧,将两个IO Ints加在一起可能是荒谬的 - 我还不确定.)(另外,如果你注意这个Maybe例子,你已经注意到"用额外的东西包装一个值"并不总是正确的.但它很难确切,准确,准确,不可理解.)

基本上,"monad"大致意味着"模式".但是,您现在拥有一个语言结构 - 语法和全部 - 而不是一本充满非正式解释和特别命名的模式的书,它允许您将新模式声明为程序中的事物.(这里的不精确是所有的模式都必须遵循一种特定的形式,所以monad并不像模式那样通用.但我认为这是大多数人都知道和理解的最接近的术语.)

这就是人们发现monad如此混乱的原因:因为它们是如此通用的概念.要问是什么使monad成为同样含糊不清的问题是什么使某事成为一种模式.

但是想一想在语言中使用语法支持对模式概念的影响:你不必阅读" 四人帮"一书并记住特定模式的构造,你只需编写一个在不可知的情况下实现这种模式的代码,一般的方式,然后你就完成了!然后,您可以重复使用此模式,例如Visitor或Strategy或Façade等等,只需通过使用它来装饰代码中的操作,而无需反复重复实现它!

所以这就是为什么理解 monad的人发现它们如此有用:它不是一些象牙塔概念,知识分子自豪于理解(好吧,当然,teehee),但实际上使代码更简单.


一书并记住特定模式的构造,你只需
有时候,"学习者"(像你一样)的解释与另一个学习者的关系比来自专家的解释更有意义.学习者认为相似:)

8> Breton..:

经过多次努力,我想我终于明白了monad.在重读了我自己对压倒性的最高投票答案的冗长批评后,我将提供这种解释.

理解monad需要回答三个问题:

    你为什么需要monad?

    什么是monad?

    monad是如何实现的?

正如我在原始评论中所指出的那样,太多的monad解释被问到第3个问题,没有,并且在真正充分覆盖问题2或问题1之前.

你为什么需要monad?

像Haskell这样的纯函数式语言与命令式语言(如C或Java)的不同之处在于,纯函数式程序不一定按特定顺序执行,一次一步.Haskell程序更类似于数学函数,您可以在其中解决任意数量的潜在订单中的"等式".这带来了许多好处,其中之一是它消除了某些类型的错误的可能性,特别是那些与"状态"有关的错误.

但是,使用这种编程方式存在一些不易解决的问题.有些东西,比如控制台编程和文件i/o,需要按特定顺序发生,或者需要维护状态.解决此问题的一种方法是创建一种表示计算状态的对象,以及将状态对象作为输入并返回新的已修改状态对象的一系列函数.

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

consolestate MyConsole = new consolestate;

所以要进行控制台编程,但是以纯函数方式,你需要在彼此内部嵌套很多函数调用.

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

以这种方式编程保持"纯粹"的功能样式,同时强制对控制台的更改以特定顺序发生.但是,我们可能希望不仅仅像上面的例子那样只做一些操作.以这种方式嵌套功能将开始变得笨拙.我们想要的是,代码与上面的内容基本相同,但写得更像这样:

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

这确实是一种更方便的编写方式.我们怎么做呢?

什么是monad?

一旦你有一个consolestate你定义的类型(例如)以及专门设计用于该类型的一系列函数,你可以通过定义像:(bind)这样的运算符将这些东西的整个包变成"monad" feed返回左侧的值,右侧的函数参数,以及lift将正常函数转换为与特定类型的绑定运算符一起使用的函数.

monad是如何实现的?

看到其他答案,似乎可以自由地进入细节.



9> Scott Wisnie..:

monad实际上是一种"类型操作符".它会做三件事.首先,它将"包装"(或以其他方式转换)一种类型的值到另一种类型(通常称为"monadic类型").其次,它将使基础类型上的所有操作(或函数)在monadic类型上可用.最后,它将支持将自己与另一个monad组合以生成复合monad.

"may monad"本质上相当于Visual Basic/C#中的"可空类型".它采用非可空类型"T"并将其转换为"Nullable ",然后定义所有二元运算符在Nullable 上的含义.

副作用以类似方式表示.创建一个结构,其中包含副作用的描述以及函数的返回值.然后,"提升"操作在函数之间传递值时复制副作用.

它们被称为"monad",而不是"类型操作符"更容易掌握的名称,原因如下:

    Monads对他们可以做的事情有限制(详情请参阅definiton).

    这些限制以及涉及三个操作的事实符合类别理论中称为monad的结构,这是数学的一个模糊分支.

    它们是由"纯粹"功能语言的支持者设计的

    纯函数式语言的支持者,比如模糊的数学分支

    因为数学是模糊的,并且monad与特定的编程风格相关联,所以人们倾向于使用monad这个词作为一种秘密握手.因此,没有人愿意投资一个更好的名字.


叹息......我不是在攻击Haskell ......我在开个玩笑.所以,我并没有真正理解为"ad hominem".是的,微积分是"设计"的.这就是为什么,例如,微积分学生被教授Leibniz符号,而不是Netwton使用的蠢事.更好的设计.好名字有助于理解很多.如果我打电话给Abelian Groups"扩张皱纹荚",你可能无法理解我.你可能会说"但这个名字是无稽之谈",没有人会称之为.对于从未听说过类别理论的人来说,"monad"听起来像胡说八道.
回复:4,5:"秘密握手"的事情是红鲱鱼.编程充满了行话.Haskell恰好在没有假装重新发现的情况下调用它的东西.如果它已经存在于数学中,为什么要为它编造一个新名称?这个名字真的不是人们没有得到monad的原因; 它们是一个微妙的概念.一般人可能理解加法和乘法,他们为什么不得到阿贝尔群的概念?因为它更抽象和更一般,而且那个人还没有完成工作来围绕这个概念.名称更改无济于事.
@Scott:对不起,如果我的广泛评论让我觉得我对Haskell有所防御.我喜欢你关于秘密握手的幽默,你会注意到我说它或多或少都是真的.:-)如果你打电话给Abelian Groups"扩张皱纹豆荚"你会犯同样的错误,试图给monad一个"更好的名字"(参见F#"计算表达式"):这个词存在,关心的人知道什么是monads是,但不是"温暖模糊的东西"(或"计算表达式").如果我理解你正确使用术语"类型操作符",那么除了monad之外还有许多其他类型的操作符.

10> George..:

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

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

Monads解决了一个问题,它也在算术中显示为零除,compose (>>).具体而言,涉及除法的计算必须检测或允许(x -> y) >> (y -> z)异常.这个要求使得在一般情况下编码这样的表达式变得混乱.

Monadic的解决方案是y通过以下方式来实现

    展开Mb式,包括(is_OK, b)作为是不是正规号码的特定值:y,bool,或bool * float.让我们称这个新的数字类型,bool.

    提供"提升"或将现有包装(Ma -> Mb) >> (Mb -> Mc)compose类型的功能("包装"的想法是内容is_OK或值可以"解包"而不会丢失信息)

    提供将"提升"或包装现有操作员的功能false转换为可在其上运行的版本bind (>>=).这样一个"提升"的操作员可能只做以下事情:

      展开提供的composition操作数并将其包含的compose运算符应用于它们然后将Bind结果"提升" 为aM

      Ma在评估期间检测操作数或异常,并通过进一步评估,生成一个a值作为断言(bind)的结果.但是,采取什么行动取决于程序员.通常,这些包装函数是编写Monad的许多功能的地方.monadic状态信息保存在包装类型本身内,包装函数检查它,并且根据函数式编程不变性方法,构造一个新的monadic值.在这种情况下M,这种一元状态信息将描述是否存在a或实际bind存在.

因此,Monad是一个扩展类型,它具有将原始类型"包装"到此扩展​​版本中的函数,以及另一个包装原始操作符的函数,以便它们可以处理这种新的扩展类型.(Monads可能是仿制药或类型参数的动机.)

事实证明,aMonad处理可以广泛应用于可以从类型扩展中受益以简化编码的情况,而不仅仅是平滑处理(或无限制).事实上,这种适用性似乎很广泛.

例如M,字面意思是表示宇宙的类型.目的是要认识到原型(a -> Mb) >>= (b -> Mc)程序返回的值不是由结果类型Mb >>= (b -> Mc)及其值"Hello World!" 完全描述的.实际上,这样的结果还包括对诸如控制台之类的设备的硬件和存储器状态的修改.例如,执行后控制台现在显示其他文本,光标位于新行上,依此类推.shell and content如果你愿意的话,这只是对这种外在影响或副作用的明确认识.

何必?

Monads允许设计严格的无状态算法并记录状态完整的算法.国家机器很复杂.例如,仅具有10比特的机器可以处于2 ^ 10个可能状态.消除多余的复杂性是功能语言的理想选择.

变量保持状态.消除"变量"应该只是填充.纯功能程序不处理变量,只处理值(尽管在Haskell文档中使用了术语'变量'),而是根据需要使用标签或符号或名称作为这些值.因此,与纯函数语言中的变量最接近的是函数接收的参数,因为它们在每次调用时接受新值.(标签是指一个值,而变量指的是一个值被保留的位置.因此,你可以修改一个变量的内容,但标签就是内容本身.最终给予一个苹果比一个包更好可能还有一个苹果.)

缺少变量是纯函数式语言使用递归而不是循环迭代的原因.递增计数器的行为涉及使用变量增加的变量以及变更时的所有不确定性,测试时的变化,应该是什么值以及何时更新,以及当多个线程可能访问该变量时的复杂性相同的变量.

不过,那又怎样?

如果没有状态,函数必须成为它的结果的声明或定义,而不是将某些潜在的状态纳入结果.本质上,函数表达式bindMHere 的命令式表达式更简单,bind不会修改a -> Mb但会创建新值.incFun甚至可以被表达式中的定义替换为Mb成为0.另一方面,monad修改状态a.无论这种修改意味着什么,Ma都可能不清楚,并且除了任何并发问题之外,最终无法在不执行程序的情况下确定.

随着时间的推移,这种复杂性在认知上会变得昂贵(2 ^ N).相反,运算符a -> b不能修改,a -> Mb而必须构造一个新值,其结果受限于并完全由值bindM定义决定bind.特别是,避免了2 ^ N复杂性爆炸.此外,为了强调并发性,Associativity不同的是bind,不能在没有注意事项共享的情况下同时调用,因为它会被每次调用修改.

为什么称它为Monad?

Monad的特征在于来自代数群理论的称为Monoid的数学结构.话虽如此,所有这意味着Monoid具有以下三个属性:

    有一个二元运算符,bind这样Associativitybinding属于某种类型f.例如1÷2 = 0.5,其中1,2和0.5都是类型g.关闭

    有一个标识元素,Ma与二进制运算符相关联,不执行任何操作bind.例如,数字运算符+和数字0,在4 + 0 = 0 + 4 = 4中.标识

    评估"细分"的顺序无关紧要:Ma.例如,数字运算符+,in f.但请注意,术语的顺序不得更改.关联性

属性三(关联性)允许通过将它们描绘成段并且独立地评估每个段(例如并行)来评估任意长度的表达.例如,g可以分段为bind.compose (>>)然后可以收集单独的结果并进行类似的进一步评估.像这样支持分段是确保正确的并发性和分布式评估的关键技术,如Google的分布式搜索算法(la map/reduce)所使用的.

属性二(Identity)允许以各种方式更容易地构造表达式,尽管它可能并不完全明显; 然而,就像早期计数系统显然不需要零一样,它可以作为空的概念用于包装空值.请注意,在类型,(x -> y) >> (y -> z),y是不是空值,而是Mb.具体而言,(is_OK, b)y,因此bool仍在身份bool * float,其中bool是任何(Ma -> Mb) >> (Mb -> Mc).

最后,有一个原因我们不再使用罗马数字...没有扩展的零或分数,无理数,负数,虚数,... ...是的,似乎我们的数字系统可以被认为是monad.


你可能会混淆*代数群理论*和*类别理论*Monad来自哪里.前者是代数群的理论,它是无关的.

11> ShreevatsaR..:

(另请参阅什么是monad的答案)

Monads的一个好动机是sigfpe(Dan Piponi)你可以发明Monad!(也许你已经拥有).还有其他的单子教程很多,其中有不少misguidedly尝试使用各种类比于"深入浅出"来解释单子:这是单子教程谬误 ; 避免他们.

正如DR MacIver所说,告诉我们为什么你的语言很糟糕:

所以,我讨厌Haskell的事情:

让我们从明显的开始.Monad教程.不,不是单子.特别是教程.他们是无尽的,夸张的,亲爱的上帝,他们是乏味的.此外,我从未见过任何令人信服的证据证明他们确实有所帮助.阅读类定义,编写一些代码,克服可怕的名字.

你说你了解Maybe monad?好的,你在路上.刚开始使用其他monad,迟早你会明白monad是什么.

[如果你是数学为主,你可能想忽略几十个教程和学习的定义,或者按照范畴论讲座 :)定义的主要部分是一个单子中号涉及到一个"类型构造",它定义了每个现有类型"T"是一种新型"MT",以及在"常规"类型和"M"类型之间来回切换的一些方法.

此外,令人惊讶的是,最好的引见一个单子实际上是早期的学术论文引入单子,菲利普·沃德勒的一个单子函数式编程.它实际上具有实用的,非平凡的激励示例,与许多人工教程不同.


有时我觉得有这么多的教程试图通过使用复杂或有用的代码来说服读者使用monad是有用的.几个月来,这阻碍了我的理解.我没有这样学习.我更喜欢看到非常简单的代码,做一些愚蠢的事情,我可以在精神上经历,我找不到这种例子.我无法知道第一个例子是否是一个解析复杂语法的monad.我可以学习它是否是一个求和整数的monad.
Wadler的论文唯一的问题是符号是不同的但我同意这篇论文非常引人注目,并且应用monad是一个明确的简洁动机.

12> 小智..:

Monads用于控制抽象数据类型对数据的流量.

换句话说,许多开发人员对集合,列表,字典(或哈希,或地图)和树木的想法感到满意.在这些数据类型中有许多特殊情况(例如InsertionOrderPreservingIdentityHashMap).

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

因此,monad只是一个控制流构造.替换monad的更好的短语是"控制类型".

因此,monad具有用于控制逻辑或语句或函数的插槽 - 数据结构中的等价物可以说某些数据结构允许您添加数据并将其删除.

例如,"if"monad:

if( clause ) then block

最简单的有两个插槽 - 一个子句和一个块.该if单子通常是建立评估条款的结果,如果不是假的,评估该块.许多开发人员在学习'if'时并没有被介绍给monad,并且没有必要理解monad来编写有效的逻辑.

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

当然,以可以迭代或遍历数据结构的相同方式,可以评估monad.

编译器可能支持也可能不支持用户定义的monad.Haskell当然可以.Ioke有一些类似的功能,虽然monad这个词没有在语言中使用.



13> Jared Updike..:

我最喜欢的Monad教程:

http://www.haskell.org/haskellwiki/All_About_Monads

(谷歌搜索"monad教程"中的170,000次点击!)

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

Haskell已经有了运算符重载[1]:它使用类型类的方式与在Java或C#中使用接口的方式非常相似,但Haskell恰好也允许使用+ &&和>等非字母数字标记作为中缀标识符.如果你的意思是"超载分号",那么只有运算符重载才能看到它[2].这听起来像是黑魔法并且要求"超出分号"的麻烦(图片很有进取心的Perl黑客得到了这个想法的风)但重点是没有monad 没有分号,因为纯功能代码不需要或允许显式排序.

这一切听起来都比它需要的复杂得多.sigfpe的文章非常酷,但是使用Haskell来解释它,这种方法无法打破鸡和蛋的问题,理解Haskell来修复Monads并理解Monads以克服Haskell.

[1]这是monad的一个独立问题,但monad使用Haskell的运算符重载功能.

[2]这也是一个过于简单化,因为链接monadic动作的运算符是>> =(发音为"bind")但是有句法糖("do")允许你使用大括号和分号和/或缩进和换行符.




14> jes5199..:

最近,我一直在以不同的方式思考Monads.我一直认为它们是以数学方式抽象出执行顺序,这使得新的多态性成为可能.

如果您正在使用命令式语言,并且按顺序编写了一些表达式,则代码ALWAYS将按此顺序运行.

在简单的情况下,当你使用monad时,感觉是一样的 - 你定义了一个按顺序发生的表达式列表.除此之外,根据您使用的monad,您的代码可能按顺序运行(如在IO monad中),同时并行运行多个项目(如List monad),它可能会中途停止(如在Maybe monad中) ,它可能会暂停一段时间以便稍后恢复(比如在Resumption monad中),它可能会从头开始倒回并开始(就像在一个Transaction monad中),或者它可能会在中途回放以尝试其他选项(如在Logic monad中) .

并且因为monad是多态的,所以可以根据您的需要在不同的monad中运行相同的代码.

此外,在某些情况下,可以将monad组合在一起(使用monad变换器)以同时获得多个功能.


,这使得新的多态性成为可能.

15> chenj7..:

我仍然是monads的新手,但我想我会分享一个我觉得非常好读的链接(带图片!!):http: //www.matusiak.eu/numerodix/blog/2012/3/11/ monads-for-the-layman / (没有隶属关系)

基本上,我从文章中得到的温暖和模糊概念是monad基本上是适配器的概念,它允许不同的函数以可组合的方式工作,即能够串起多个函数并混合和匹配它们而不必担心不一致的返回类型等.因此,当我们尝试制作这些适配器时,BIND功能负责将苹果与苹果和橙子与橙子保持在一起.和升降功能是负责以"低级别"功能和"升级"他们与BIND功能工作,可组合为好.

我希望我做对了,更重要的是,希望这篇文章对monad有一个有效的观点.如果不出意外,这篇文章有助于激发我对学习Monad的兴趣.



16> 小智..:

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

在jQuery文档本身并不指"单子"一词,但谈到了"构建者模式",这可能是比较熟悉的.这并没有改变你有一个合适的monad甚至没有意识到它的事实.


JQuery强调不是monad.链接的文章是错误的.

17> thSoft..:

Monads不是隐喻,而是一种从普通模式中产生的实用抽象,正如Daniel Spiewak解释的那样.



18> Alex..:

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



19> Mateusz Char..:

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



20> Benjol..:

如果我正确理解,则IEnumerable是从monad派生的。我想知道对于C#世界中的那些人来说这是否可能是一个有趣的方法?

对于它的价值,这里有一些帮助我的教程的链接(不,我仍然不知道什么是monads)。

http://osteele.com/archives/2007/12/overloading-semicolon

http://spbhug.folding-maps.org/wiki/MonadsEn

http://www.loria.fr/~kow/monads/



21> Curt J. Samp..:

在了解有关时,这两件事对我有所帮助:

第8章,"功能解析器",来自Graham Hutton的书" Haskell编程".实际上,这根本没有提到monad,但是如果你可以完成章节并真正理解其中的所有内容,特别是如何评估一系列绑定操作,你就会理解monad的内部结构.期待这需要几次尝试.

All About Monads教程.这给出了几个很好的例子,我不得不说Appendex中的类比我为我工作.



22> Dmitry..:

Monoid似乎是确保Monoid和受支持类型上定义的所有操作始终在Monoid内返回支持类型的东西.例如,任何数字+任何数字=数字,没有错误.

除非接受两个小数,然后返回一个小数,它将除以零定义为无穷大的haskell somewhy(这恰好是一个小数)...

在任何情况下,看起来Monads只是一种确保你的操作链以可预测的方式运行的方法,并且声称为Num - > Num的函数,由另一个Num-> Num函数组成,用x调用说,发射导弹.

另一方面,如果我们有一个发射导弹的功能,我们可以用其他功能组成它,这也发射导弹,因为我们的意图很明确 - 我们想发射导弹 - 但它不会尝试出于某些奇怪的原因打印"Hello World".

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

如果我有main,我希望它做一系列动作,我运行程序的原因是产生效果 - 通常是IO.因此,我可以在主要链接IO操作,以便 - 做IO,没有别的.

如果我尝试做一些不"返回IO"的事情,那么程序会抱怨链条没有流动,或者基本上"这与我们正在尝试做什么有关 - 一个IO行动",它似乎是强制的程序员保持他们的思路,不偏离并考虑发射导弹,同时创建排序算法 - 不流动.

基本上,Monads似乎是编译器的一个提示,"嘿,你知道这个函数在这里返回一个数字,它实际上并不总是有效,它有时可以生成一个数字,有时甚至没有任何东西,只需保留它心神".知道这一点,如果你试图断言一个monadic动作,monadic动作可能会作为一个编译时异常说"嘿,这实际上不是一个数字,这可以是一个数字,但你不能假设这一点,做一些事情确保流量可以接受." 这在很大程度上阻止了不可预测的程序行为.

看起来monad不是关于纯度,也不是控制,而是关于维护所有行为可预测和定义的类别的标识,或者不编译.当你被期望做某事时你不能做任何事情,如果你不想做任何事情(可见),你就无法做某事.

我想到Monads的最大原因是 - 去看一下Procedural/OOP代码,你会注意到你不知道程序在哪里开始,也没有结束,所有你看到的都是大量的跳跃和大量的数学运算,魔法和导弹.您将无法维护它,如果可以,您将花费大量时间将整个程序包裹在整个程序中,然后才能理解它的任何部分,因为此上下文中的模块化基于相互依赖的"部分"代码,其中代码被优化为尽可能相关以保证效率/相互关系.Monads非常具体,定义明确,并确保程序流程可以分析,并隔离难以分析的部分 - 因为它们本身就是monad.monad似乎是一个"易于理解的单位,可以预测它的完全理解" - 如果你理解"可能"monad,除了"Maybe"之外它没有任何可能做的事情,这看似微不足道,但在大多数非monadic中代码,一个简单的功能"helloworld"可以发射导弹,什么也不做,或破坏宇宙,甚至扭曲时间 - 我们不知道也不保证它是什么.Monad保证它是什么.这是非常强大的.

"现实世界"中的所有东西似乎都是单子,因为它受到明确的可观察法则的约束,可防止混淆.这并不意味着我们必须模仿这个对象的所有操作来创建类,而是我们可以简单地说"一个正方形是一个正方形",只是一个正方形,甚至不是一个矩形,也不是一个圆形,并且"一个正方形有区域它的一个现有尺寸的长度乘以它自己.无论你有什么方形,如果它是2D空间中的正方形,它的面积绝对不能是它的长度平方,它几乎是无足轻重的证明.这非常强大,因为我们不需要做出断言来确保我们的世界就是这样,我们只是利用现实的含义来阻止我们的计划脱离轨道.

我几乎可以肯定是错的,但我认为这可以帮助那里的人,所以希望它有助于某人.



23> samthebest..:

在Scala的上下文中,您会发现以下内容是最简单的定义.基本上flatMap(或bind)是'associative'并且存在一个标识.

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
  }
}

例如

// 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



24> Jordan..:

这个答案从一个激励性的例子开始,通过例子,推导出一个monad的例子,并正式定义"monad".

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

f() := 
g() := 
wrap(x)          := 

f获取一对有序的表单并返回一个有序对.它使第一个项目保持不变并附"called f. "加到第二个项目.与...相同g.

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

  f(g(wrap(x)))
= f(g())
= f()
= 

您不喜欢这样的事实,fg负责将自己的日志消息附加到以前的日志记录信息.(想象一下为了论证而不是追加字符串,f并且g必须在对的第二项上执行复杂的逻辑.在两个或更多个不同的函数中重复那个复杂的逻辑会很痛苦.)

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

f(x)    := 
g(x)    := 
wrap(x) := 

但看看你撰写时会发生什么:

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

问题是一对传递给函数并不能满足您的需求.但是,如果你可以一对输入函数,该怎么办:

  feed(f, feed(g, wrap(x)))
= feed(f, feed(g, ))
= feed(f, )
= 

feed(f, m)为"feed minto f".为了养活一对成功能f通过 xf,拿到了的f,并返回.

feed(f, ) := let  = f(x)
                          in  

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

第一:如果包裹一个值,然后进料所得的一对成一个函数:

  feed(f, wrap(x))
= feed(f, )
= let  = f(x)
  in  
= let  = 
  in  
= 
= 
= f(x)

这与传递给函数相同.

第二:如果你喂一对wrap:

  feed(wrap, )
= let  = wrap(x)
  in  
= let  = 
  in  
= 
= 

这不会改变这对.

第三:如果你定义一个接受xg(x)输入的函数f:

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

并喂一对:

  feed(h, )
= let  = h(x)
  in  
= let  = feed(f, g(x))
  in  
= let  = feed(f, )
  in  
= let  = let  = f(x)
                     in  
  in 
= let  = let  = 
                     in  
  in 
= let  = 
  in 
= 
= feed(f, )
= feed(f, feed(g, ))

这与将该对进g料并将所得的对喂入其中相同f.

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

什么类型的价值?那么,这取决于什么类型的价值x.如果x是类型t,那么您的对是"对t和字符串" 类型的值.拨打该类型M t.

M是一个类型构造函数:M单独不引用类型,但是M _一旦用类型填充空白就引用类型.An M int是一对int和一个字符串.An M string是一对字符串和一个字符串.等等.

恭喜你,你创造了一个monad!

在形式上,你的monad是元组.

monad是一个元组,其中:

M 是一个类型构造函数.

feed采取a(取a t并返回a的函数M u)和a M t并返回a M u.

wrap拿a v并返回一个M v.

t,, uv是任何三种类型,可能相同也可能不相同.monad满足您为特定monad证明的三个属性:

馈送包裹t成一个函数是相同的传递展开的t入功能.

形式上: feed(f, wrap(x)) = f(x)

喂食的M twrap什么都不做的M t.

形式上: feed(wrap, m) = m

M t(调用它m)送入一个函数

tg

得到一个M u(称之为n)g

饲料nf

是相同的

饲养mg

获得ng

饲养nf

形式:feed(h, m) = feed(f, feed(g, m))在哪里h(x) := feed(f, g(x))

通常,feed被称为bind(>>=Haskell中的AKA )并被wrap称为return.



25> Jonas..:

我将尝试Monad在Haskell的背景下进行解释。

在函数编程中,函数组成很重要。它允许我们的程序包含小的,易于阅读的功能。

假设我们有两个功能:g :: Int -> Stringf :: String -> Bool

我们可以做(f . g) x,与一样f (g x),其中x一个Int值。

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

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

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

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

我们可以撰写fg.运营商,但现在我们不能组成f1g1使用.。问题是我们不能直接将上下文中的值传递给期望不在上下文中的值的函数。

如果我们引入一个运算符来组成g1and f1,这样我们可以写,那不是很好(f1 OPERATOR g1) x吗?g1返回上下文中的值。该值将脱离上下文并应用于f1。是的,我们有这样的运营商。是<=<

>>=尽管语法略有不同,但我们还有一个运算符可以为我们做完全相同的事情。

我们写:g1 x >>= f1g1 x是一个Maybe Int值。该>>=操作有助于采取Int值了“也许-不存在”断章取义,并将其应用到f1。的结果f1Maybe Bool,将是整个>>=运算的结果。

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

总而言之,Monad类型类定义了>>=运算符,该运算符允许我们将上下文中的值(我们称这些单价)传递给在上下文中不期望值的函数。上下文将得到照顾。

如果这里要记住一件事,那就是Monad允许函数组合涉及上下文中的值



26> 小智..:

tl; dr

{-# 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

序幕

$函数的应用运算符

forall a b. a -> b

规范定义

($) :: (a -> b) -> a -> b
f $ x = f x

infixr 0 $

就Haskell原始函数应用f xinfixl 10)而言。

组成.定义$

(.) :: (b -> c) -> (a -> b) -> (a -> c)
f . g = \ x -> f $ g x

infixr 9 .

并满足等效条件 forall f g h.

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

.是关联的,id是左右身份。

克莱斯里三人组

在编程中,monad是函子类型构造函数,具有monad类型类的实例。定义和实现有几种等效的变体,每种变体对monad抽象的理解都略有不同。

一个仿函数是一种构造f一种* -> *与仿函数类型的类的实例。

{-# LANGUAGE KindSignatures #-}

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

除遵循静态强制类型协议外,函子类型类的实例还必须遵守代数函子定律 forall f g.

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

函子计算具有以下类型

forall f t. Functor f => f t

一个计算c r包括在结果 r的上下文 c

一元单子函数或Kleisli箭头具有以下类型

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

Kleisi箭头是具有一个参数a并返回一元计算的函数m b

Monads是按照Kleisli三元规范定义的 forall m. Functor m =>

(m, return, (=<<))

实现为类型类

class Functor m => Monad m where
   return :: t -> m t
   (=<<)  :: (a -> m b) -> m a -> m b

infixr 1 =<<

所述Kleisli身份 return是Kleisli箭头促进的值t成一元上下文mExtensionKleisli应用程序 =<<将Kleisli箭头应用于a -> m b计算结果m a

Kleisli组成 <=<根据扩展定义为

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

infixr 1 <=<

<=< 组成两个Kleisli箭头,将左箭头应用于右箭头应用的结果。

monad类型类的实例必须遵守monad法则,就Kleisli组成而言,其用法最为优美:forall f g h.

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

<=<是关联的,return是左右身份。

身分识别

身份类型

type Id t = t

是类型上的标识函数

Id :: * -> *

解释为函子,

   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)

在规范的Haskell中,标识monad被定义

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

选项

选项类型

data Maybe t = Nothing | Just t

Maybe t对不一定会产生结果的t计算进行编码,即可能“失败”的计算。选项monad已定义

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

a -> Maybe b仅当Maybe a产生结果时才应用于结果。

newtype Nat = Nat Int

可以将自然数编码为大于或等于零的那些整数。

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

减法不会关闭自然数。

(-?) :: Nat -> Nat -> Maybe Nat
(Nat n) -? (Nat m) = toNat (n - m)

infixl 6 -?

选项monad涵盖了异常处理的基本形式。

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

清单

列表单子,在列表类型之上

data [] t = [] | t : [t]

infixr 5 :

并且其附加的monoid操作“附加”

(++) :: [t] -> [t] -> [t]
(x : xs) ++ ys = x : xs ++ ys
[]       ++ ys = ys

infixr 5 ++

非线性计算进行编码[t]产生自然0, 1, ...的结果t

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)
   _ =<< []       = []

扩展将将Kleisli箭头应用于的元素所得到的所有列表=<<连接到单个结果列表中。++[b]f xa -> [b][a][b]

让一个正整数的正确除数n

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

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

然后

forall n.  let { f = f <=< divisors } in f n   =   []

在定义monad类型类而不是扩展时=<<,Haskell标准使用其翻转字符bind运算符>>=

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

为简单起见,此说明使用类型类层次结构

class              Functor f
class Functor m => Monad m

在Haskell中,当前的标准层次结构为

class                  Functor f
class Functor p     => Applicative p
class Applicative m => Monad m

因为不仅每个monad都是函子,而且每个应用程序都是函子,每个monad也都是应用程序。

使用列表monad,命令伪代码

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

大致翻译为do块

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

等效的monad理解

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

和表达

[1 .. 10] >>= (\ a ->
   [1 .. 10] >>= (\ b ->
      let p = a * b in
         guard (even p) >>       -- [ () | even p ] >>
            return p
      )
   )

注解和monad理解是嵌套绑定表达式的语法糖。bind操作符用于单子结果的本地名称绑定。

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

哪里

(&) :: a -> (a -> b) -> b
(&) = flip ($)

infixl 0 &

保护功能已定义

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

其中单元类型或“空的元组”

data () = ()

可以使用类型类抽象化支持选择失败的加法蒙纳德

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.

     k <|> fail  =  k
     fail <|> l  =  l
(k <|> l) <|> m  =  k <|> (l <|> m)

并且fail是添加单核的吸收//灭零元素

_ =<< fail  =  fail

如果在

guard (even p) >> return p

even p为真,则警卫队产生[()],并根据的定义>>,产生局部常数

\ _ -> return p

适用于结果()。如果为假,则后卫将生成列表monad的fail[]),将其应用于Kleisli箭头不会产生任何结果>>,因此将p其跳过。

臭名昭著的是,使用monad对状态计算进行编码。

状态处理器是一个函数

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

转换状态st并产生结果t。该状态 st可以是任何东西。没有任何内容,标志,计数,数组,句柄,机器,世界。

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

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

状态处理程序monad是仁慈的* -> *函子State st。状态处理器monad的Kleisli箭头是函数

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

在规范的Haskell中,定义了状态处理器monad的惰性版本

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

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

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有状态单子抽象方法提供:

{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies #-}

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

m -> st声明状态类型对monad 的功能依赖 ; 一个,例如,将确定的状态类型是唯一。stmState tt

instance Stateful (State st) st where
   get :: State st st
   get = State $ \ s -> (s, s)

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

void在C中类似使用的单位类型。

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 通常与记录字段访问器一起使用。

状态monad等效于变量线程

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

其中s0 :: Int,是同样参照透明的,但无限地更加优雅和实用

(flip run) 34
   (do
      modify (+ 1)
      n <- gets (* 12)
      modify (+ 7)
      return (show n)
   )

modify (+ 1)是类型的计算State Int (),除了其作用等同于return ()

(flip run) 34
   (modify (+ 1) >>
      gets (* 12) >>= (\ n ->
         modify (+ 7) >>
            return (show n)
      )
   )

结合性的莫纳德定律可以写成 >>= forall m f g.

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

要么

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                 }
}                    }

像面向表达式的编程(例如Rust)一样,块的最后一条语句表示其产量。绑定运算符有时称为“可编程分号”。

一元模拟来自结构化命令式编程的迭代控制结构原语

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

输入输出

data World

I / O世界状态处理器monad是纯Haskell与现实世界,功能性说明性和命令性操作语义的协调。与实际严格执行的近似:

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

不纯净的原语促进了交互

getChar         :: IO Char
putChar         :: Char -> IO ()
readFile        :: FilePath -> IO String
writeFile       :: FilePath -> String -> IO ()
hSetBuffering   :: Handle -> BufferMode -> IO ()
hTell           :: Handle -> IO Integer
. . .              . . .

使用IO原语的代码的杂物由类型系统永久地协议化。因为纯净真棒,所以发生在里面IO,留在里面IO

unsafePerformIO :: IO t -> t

或者至少应该如此。

Haskell程序的类型签名

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

扩展到

World -> ((), World)

改变世界的功能。

结语

类别对象是Haskell类型,而态态是Haskell类型之间的函数,类别为“快速和宽松” Hask

函子T是从类别C到类别的映射D; 对于一个对象中C的每个对象D

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

并且对于一个态中C的每个态D

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

其中XY在对象CHomC(X, Y)同态类的所有态射的X -> YC。仿函数必须保留态射特性和组合物,所述的“结构” CD

                    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三重给定


终结者的

T : C -> C

f),身份同构etareturn)和扩展运算符*=<<)。

每个Kleisli态 Hask

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

由扩展运营商

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

Hask的Kleisli类别中被赋予了态射

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

克莱斯里(Kleisli)类别的构成以.T扩展名给出

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

并满足类别公理

       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

应用等价转换

     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

在扩展方面被规范地给出

               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

也可以mu在编程中称为Monad,而不是根据Kleislian扩展定义而是自然转换join。monad被定义muCendofunctor 的category 的三元组

     T :  C -> C
     f :: * -> *

和两个自然转变

   eta :  Id -> T
return :: t  -> f t

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

满足等价

推荐阅读
wangtao
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有