What is a monad?
在最近短暂地看了哈斯克尔之后,对于单子的本质是什么,一个简短、简洁、实用的解释是什么?
我发现我遇到的大多数解释都很难理解,而且缺乏实际细节。
第一:如果你不是数学家,Monad这个词有点空虚。另一个可供选择的术语是计算生成器,它更能描述它们的实际用途。好的。
你需要一些实际的例子:好的。
例1:列表理解:好的。
1 |
此表达式返回1到10范围内所有奇数的双精度数。非常有用!好的。
事实证明,对于列表monad中的某些操作,这实际上只是语法上的糖分。同样的列表理解可以写成:好的。
甚至:好的。
例2:输入/输出:好的。
这两个例子都使用monad,也就是计算生成器。共同的主题是Monad链以某种特定的、有用的方式进行操作。在列表理解中,操作被链接起来,这样,如果一个操作返回一个列表,那么对列表中的每个项目执行以下操作。另一方面,IO monad按顺序执行操作,但同时传递一个"隐藏变量",表示"世界状态",允许我们以纯粹的功能方式编写I/O代码。好的。
结果表明,链接操作模式非常有用,并且在Haskell中用于许多不同的事情。好的。
另一个例子是异常:使用
列表理解语法和do符号都是使用
示例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)) |
示例4:异步编程好的。
上面的例子在haskell中,但事实证明f也支持monads。这个例子是从Don Syme那里偷来的:好的。
1 2 3 4 5 6 |
此方法获取网页。穿孔行是使用
在大多数其他语言中,您必须为处理响应的行显式地创建一个单独的函数。
它们是如何工作的好的。
那么Monad怎么能做所有这些花哨的控制流的事情呢?在do块(或在f中调用的计算表达式)中实际发生的情况是,每个操作(基本上是每一行)都被包装在一个单独的匿名函数中。然后,使用
例如,这是示例2中IO代码的扩展版本:好的。
1 2 3 4 |
这更难看,但实际上发生的事情也更明显。
注意,不同类型的
在如何将值从一个操作传递到下一个操作上还有一些额外的聪明之处,但这需要对haskell类型系统进行更深入的解释。好的。
总结好的。
在haskell术语中,monad是一个参数化类型,它是monad类型类的一个实例,monad类定义
本身,
为什么单子很难?好的。型
对于哈斯克尔的许多学习者来说,单子是他们遇到的障碍,就像砖墙一样。不是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是什么"有点像说"数字是什么?"我们总是用数字。但想象一下你遇到了一个对数字一无所知的人。你怎么解释这些数字?你怎么开始描述为什么这可能有用?
什么是单子?简而言之:这是一种将操作链接在一起的特定方法。
本质上,您正在编写执行步骤,并将它们与"绑定函数"链接在一起。(在haskell中,它名为
所以bind函数就像一个分号;它分隔了进程中的步骤。bind函数的工作是获取上一步的输出,并将其输入下一步。
听起来不太难,对吧?但是有不止一种单子。为什么?怎么用?
好吧,bind函数只需从一个步骤中获取结果,并将其提供给下一个步骤。但如果这就是蒙娜德所做的一切…这实际上不是很有用。这一点很重要,要理解:每一个有用的单子除了做单子之外,还做其他的事情。每一个有用的单子都有一种"特殊的力量",这使它独一无二。
(一个没有什么特别之处的单子被称为"身份单子"。这听起来像是一个毫无意义的东西,但结果却不是…但这是另一个故事&trade;)
基本上,每个monad都有自己的bind函数实现。您可以编写一个绑定函数,这样它就可以在执行步骤之间执行循环操作。例如:
如果每个步骤都返回一个成功/失败指示器,那么只有在前一个步骤成功时,才能让bind执行下一个步骤。这样,失败的步骤将"自动"中止整个序列,而无需您进行任何条件测试。(失败的单子。)
扩展这个概念,您可以实现"异常"。(错误Monad或异常Monad。)因为您自己定义它们,而不是将其作为语言功能,所以您可以定义它们的工作方式。(例如,您可能希望忽略前两个异常,只有在引发第三个异常时才会中止。)
您可以使每个步骤返回多个结果,并让绑定函数循环遍历这些结果,将每个结果送入下一步。这样,在处理多个结果时,就不必一直在各地编写循环。绑定函数"自动"为您完成所有这些。(单子上的单子。)
除了将"结果"从一个步骤传递到另一个步骤之外,绑定函数还可以传递额外的数据。这些数据现在不会显示在源代码中,但是您仍然可以从任何地方访问它,而无需手动将其传递给每个函数。(读者Monad。)
您可以这样做,以便替换"额外数据"。这允许您模拟破坏性更新,而实际上不进行破坏性更新。(州里的蒙娜德和它的表兄,作家蒙娜德。)
因为你只是在模拟破坏性的更新,你可以做一些真正破坏性的更新是不可能的事情。例如,您可以撤消上一次更新,或者恢复到旧版本。
你可以制作一个单子,在那里计算可以暂停,所以你可以暂停你的程序,进入并修补内部状态数据,然后继续它。
您可以将"continuations"实现为monad。这可以让你打破人们的想法!
所有这些和更多的都可以用单子。当然,没有单子,所有这些都是完全可能的。使用Monads非常简单。
事实上,与蒙娜斯的共同理解相反,他们与国家无关。monad只是包装东西的一种方法,它提供了在不拆开包装的情况下对包装的东西进行操作的方法。
例如,可以在haskell中创建一个类型来包装另一个类型:
1 | data Wrapped a = Wrap a |
包装我们定义的东西
要在不展开的情况下执行操作,假设您有一个函数
这就是要理解的一切。然而,事实证明,这种提升有一个更一般的功能,即
1 2 | bind :: (a -> Wrapped b) -> (Wrapped a -> Wrapped b) bind f (Wrap x) = f x |
很酷的是,这是一个非常普通的模式,它会突然出现在所有地方,以一种纯粹的方式封装状态只是其中之一。
关于如何使用monad来引入功能依赖性,从而控制评估顺序的一篇好文章,就像在Haskell的IO monad中使用的一样,请查看内部的IO。
至于了解单子,不要太担心。读一些你觉得有趣的东西,如果你不马上理解的话,不要担心。然后,像哈斯克尔这样的语言潜水就是一条出路。单子就是这样的东西之一,通过练习,理解会慢慢进入你的大脑,有一天你突然意识到你理解了它们。
但是,你本可以发明单子的!
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.
< /块引用>
monad是具有两个操作的数据类型:
>>= (akabind )和return (akaunit )。return 采用任意值并创建monad实例。>>= 以monad为例,并在其上映射一个函数。(您已经看到monad是一种奇怪的数据类型,因为在大多数编程语言中,您无法编写一个接受任意值并从中创建类型的函数。monads使用一种参数多态性。)在haskell表示法中,monad接口被写入
这些操作应该遵守某些"法律",但这并不十分重要:"法律"只是编纂了操作的合理实施方式(基本上,
>>= 和return 应该就值如何转换为单实例达成一致,>>= 是关联的)。monad不仅仅与状态和I/O有关:它们抽象了一种通用的计算模式,包括处理状态、I/O、异常和非确定性。可能最简单的单子是列表和选项类型:
1
2
3
4
5
6
7
8
9其中
[] 和: 为列表构造器,++ 为连接运算符,Just 和Nothing 为Maybe 构造器。这两个单体都封装了各自数据类型上的通用和有用的计算模式(注意,两者都与副作用或I/O无关)。你真的需要花点时间写一些不平凡的haskell代码来理解monads是关于什么以及为什么它们是有用的。
你首先应该理解什么是函子。在此之前,请理解高阶函数。
高阶函数只是一个以函数为参数的函数。
函式是存在一个高阶函数的任何类型构造
T ,称为map ,它将a -> b 类型的函数(给定a 和b 两种类型)转换为T a -> T b 的函数。此map 函数还必须遵守同一性和组合律,以便下列表达式对所有p 和q 都返回true(haskell表示法):例如,如果一个名为
List 的类型构造函数配备了一个符合上述规律的(a -> b) -> List a -> List b 类型函数,那么它就是一个函数。唯一实际的实现是显而易见的。生成的List a -> List b 函数迭代给定的列表,为每个元素调用(a -> b) 函数,并返回结果列表。monad本质上只是一个函数
T ,有两种额外的方法:join ,T (T a) -> T a 型和unit 型(有时称为return 、fork 或pure ,a -> T a 型。对于haskell中的列表:
1
2 join :: [[a]] -> [a]
pure :: a -> [a]为什么有用?因为您可以,例如,使用返回列表的函数对一个列表执行
map 。join 获取结果列表并将其连接起来。List 是单胞菌,因为这是可能的。您可以编写执行
map 的函数,然后编写join 的函数。此函数称为bind 、flatMap 、(>>=) 或(=<<) 。这通常是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。安整数或字符串)的附加信息,所以冰某的脸颊。
例如,这可能是一个额外的信息
Maybe 或IO 。
你有一些运营商,允许你操作的数据包。沿这段额外的信息。这些运营商使用的附加信息的两个《决定如何改变行为的操作在包装的价值。
例如,可以
Maybe Int A或AJust Int Nothing 。现在,如果你添加一Maybe Int 两个Maybe Int 放映会的检查,如果他们看到的都是Just Int s里面,如果这样,将unwrap《Int s,通过他们的加法操作,重新包装的resultingInt 到一个新的Just Int (这是一个有效的Maybe Int ),和一Maybe Int 这样的回报。但如果他们是一个一个Nothing 在里面,这只是运营商将立即返回Nothing 了,这是一个有效的Maybe Int 。这样的方式,你可以pretend,你是完全正常的Maybe Int s数和perform常规数学对他们。如果你是一个让你Nothing 方程,将静态的生产权,没有结果,你有两个站Nothing 所有支票。
但以冰为
Maybe 真的会发生什么。如果我是个IO 额外的信息,然后,定义为IO s特殊的放映会,而不是所谓的,它可以做一些完全不同的表演过《之外。(好吧,第二IO Int s增冰可能nonsensical——我不确定,我(也)。注意,如果你支付的Maybe example,你有noticed"包装,A值与冰的额外的东西",并不总是正确的。但它的硬的精确和准确的,正确的,没有被inscrutable)。
basically"单子"roughly均值的"模式"。但是,而不是一本书的全鸭informally explained专门命名方式,你现在有一个语言的语法构造和所有————这是允许你为新模式的两个DECLARE的事情在你的程序。(《冰imprecision这里所有的方式有两种,一种特殊的单子,所以A是不通用的,作为一个全新的AA型。但我认为这是closest术语,大多数人知道和了解。)
这就是为什么人们找到冰渣confusing单子:因为他们是这样一种通用的概念。问:是什么让一个单子的冰similarly浪潮为问是什么让一个A型。
我觉得这个含义大学具有句法的语言支持的想法(而不是A型:具有两个读《四人帮》和《建设部memorise特别是A型,你只要写代码,implements这个模式在一个通用的agnostic通一次,然后你做了!这是你可以重用的类型、游客或战略或什么的?艾德或什么的,只是在城市作战decorating用它在你的代码中,有两个没有重新实施它的鸭子!
所以这是为什么中国人了解它们,所以找到有用的单子:这不是象牙塔的一些概念,snobs自豪自己对知识的理解(这也是好的,当然,teehee),但实际上使简单的代码。
经过努力,我想我终于明白了单子。在重读了我自己对压倒性的最高投票答案的冗长批评之后,我将给出这个解释。
要理解单子,需要回答三个问题:
你为什么需要一个单子? 什么是单子? Monad是如何实现的? 正如我在最初的评论中所指出的,太多的单子解释在问题3中被卷入,没有,而且在真正充分涵盖问题2或问题1之前。
你为什么需要一个单子?
像Haskell这样的纯函数语言与命令语言(如C或Java)不同,纯函数程序不一定按特定的顺序执行,一次一步。haskell程序更类似于一个数学函数,在这个函数中,你可以用任意数量的势序来解"方程"。这带来了许多好处,其中包括消除了某些类型的错误的可能性,特别是与"状态"等相关的错误。
但是,有一些问题并不能用这种编程方式直接解决。有些事情,比如控制台编程和文件I/O,需要以特定的顺序发生,或者需要保持状态。处理这个问题的一种方法是创建一种表示计算状态的对象,以及一系列以状态对象为输入的函数,并返回一个新的修改过的状态对象。
因此,让我们创建一个假设的"状态"值,它表示控制台屏幕的状态。这个值的构造方式并不重要,但我们假设它是一个字节长度的ASCII字符数组,表示屏幕上当前可见的内容,以及一个数组,表示用户输入的最后一行伪代码。我们已经定义了一些接受控制台状态、修改它并返回新控制台状态的函数。
1 consolestate MyConsole = new consolestate;因此,要进行控制台编程,但是以一种纯粹的函数方式,您需要在每个对象内部嵌套大量的函数调用。
1以这种方式编程可以保持"纯粹"的功能风格,同时强制以特定的顺序对控制台进行更改。但是,我们可能希望像上面的例子一样,一次只做几个操作。这样的嵌套函数将开始变得笨拙。我们想要的是基本上和上面一样的代码,但是写得更像这样:
1
2
3
4这确实是一种更方便的书写方式。但我们该怎么做呢?
什么是单子?
一旦您定义了一个类型(如
consolestate )以及一系列专门针对该类型进行操作的函数,您就可以通过定义一个像: (bind)这样的运算符,将这些内容的整个包转换为一个"monad",该运算符会自动将其左侧的返回值输入到右侧的函数参数中,并且lift 运算符,它将普通函数转换为与特定类型的绑定运算符一起工作的函数。Monad是如何实现的?
看看其他的答案,这似乎很容易跳到细节。
(另请参见"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是关于组合对函数值(高阶函数)进行操作的函数。在处理高阶函数(接受或返回函数的函数)时,必须自定义或定制组合,以便在计算组合时评估操作数。这种评估过程可能很奇特,例如收集异步过程的结果。尽管如此,这种裁剪还是可以按照一定的模式进行。该模式的一个版本称为monad,并遵循非常多的代数加法。特别是,对于以下内容,这种高阶函数将被视为表达式中的数学运算符,接受其他部分应用的运算符,因此,
1+ ( 2* ( 3/ ( 7+ (..) ) ) ) 中的函数1+2*、3/、7+。好的。monads处理的问题在算术中也显示为被零除,
DivByZero 。具体地说,涉及除法的计算必须检测或允许DivByZero 例外。这一要求使得在一般情况下对此类表达式进行编码变得混乱。好的。一元解决方案是通过执行以下操作来接受
DivByZero 。好的。扩展 Number 类型,将DivByZero 作为一个非常规数字的特定值包括:NaN 、Infinity 或Null 。我们称这个新的数字类型为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 而是必须构造一个新值,其结果仅限于并完全由x 和1 的值和+ 的定义决定。特别是,避免了2^n复杂性爆炸。此外,为了强调并发性,与incFun 不同,incImp 不能在没有关于参数共享的预防措施的情况下并发调用,因为它会被每次调用修改。好的。为什么叫它Monad?好的。
单子的特点是一个数学结构称为单子从代数群理论。这就是说,它的意思是一个单体具有以下三个性质:好的。
具有二元运算符 * ,因此x, y, and z 的x * 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 = DivByZero 而nn + 0 = 0 + nn = nn 因此0 仍然是+ 下的身份,其中nn 是任何Nullable 下的身份。好的。最后,我们不再使用罗马数字是有原因的……没有对零或分数、无理数、负数、虚数的扩展适应……是的,似乎我们的数字系统可以被视为单元数。好的。好啊。
monad实际上是"类型运算符"的一种形式。它可以做三件事。首先,它将"包装"(或以其他方式转换)一个类型的值到另一个类型(通常称为"单类型")。其次,它将使底层类型上的所有操作(或函数)都在一元类型上可用。最后,它将支持将自己与另一个单子结合起来生成复合单子。
在VisualBasic/C中,"maybe monad"本质上等同于"可以为空的类型"。它接受一个不可为空的类型"t",并将其转换为"nullable
",然后定义所有二进制运算符在nullable 上的含义。 副作用的表现类似。创建了一个结构,它将副作用的描述与函数的返回值放在一起。然后,"提升"操作在函数之间传递值时复制副作用。
它们被称为"monads",而不是更容易理解"类型运算符"的名称,原因有以下几种:
单子对他们能做什么有限制(详见定义)。 这些限制,加上涉及三个运算的事实,符合范畴论中被称为单子的东西的结构,这是数学中一个模糊的分支。 它们是由"纯"功能语言的支持者设计的。 纯函数语言的拥护者,如数学的模糊分支 因为数学是模糊的,monad与特定的编程风格相关,人们倾向于使用monad这个词作为一种秘密的握手。正因为如此,没有人费心投资一个更好的名字。
型monad用于控制数据流的抽象数据类型。
换句话说,许多开发人员对集合、列表、字典(或散列或映射)和树的概念都很满意。在这些数据类型中,有许多特殊情况(例如insertionorderPreventingIdentityHashMap)。
然而,当面对程序"流"时,许多开发人员没有接触到比if、switch/case、do、while、goto(grr)和(可能)闭包更多的构造。
因此,monad只是一个控制流结构。替换monad的更好的短语是"control type"。
因此,monad具有用于控制逻辑、语句或函数的插槽——数据结构中的等效项是,某些数据结构允许您添加和删除数据。
例如,"if"monad:
1 if( clause ) then block最简单的有两个槽——一个子句和一个块。
if monad通常用于评估子句的结果,如果不是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的兴趣。
除了上面的优秀答案之外,我还为您提供以下文章(Patrick Thomson)的链接,该文章通过将概念与JavaScript库jQuery(及其使用"方法链接"来操作DOM的方法)相关联来解释Monads:jquery是monad
jquery文档本身并不引用术语"monad",而是讨论可能更熟悉的"构建器模式"。这并不能改变这样一个事实,那就是你有一个合适的单子,也许你甚至没有意识到。
正如丹尼尔·斯皮瓦克解释的那样,单子并不是隐喻,而是一种从普通模式中产生的实际有用的抽象。
型在实践中,monad是函数组合运算符的自定义实现,它处理副作用和不兼容的输入和返回值(用于链接)。
monad是将共享公共上下文的计算组合在一起的一种方法。这就像是建立一个管道网络。在构建网络时,没有数据流过它。但当我将所有的位与"bind"和"return"混合在一起之后,我调用了类似于
runMyMonad monad data 的东西,数据通过管道流动。
型这个答案以一个激励性的例子开始,通过这个例子工作,派生出一个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.">号
您不喜欢这样一个事实:
f 和g 负责将自己的日志消息附加到以前的日志信息中。(为了论证的目的,假设f 和g 必须在对的第二项上执行复杂的逻辑,而不是附加字符串。在两个或多个不同的函数中重复这种复杂的逻辑是很痛苦的。)您更喜欢编写更简单的函数:
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 是什么类型的值。如果x 是t 型,那么您的对就是"t 和string"型的值。称之为M t 型。EDOCX1 9是一种类型的构造函数:EDCOX1,9,不单独引用一个类型,但是EDCOX1×32 }是用一个类型填充空白之后的一个类型。
M int 是一对int和一个字符串。M string 是一对字符串和一个字符串。等。恭喜你,你创造了一个单子!
正式来说,你的单子是tuple
。 monad是一个tuple
,其中: 百万千克1
m 是一个类型构造函数。百万千克1百万千克1feed 接受(接受t 并返回M u 的函数)和M t 并返回M u 的函数。百万千克1百万千克1wrap 接受v 并返回M v 。百万千克1型
t 、u 和v 是可能相同或可能不同的三种类型。monad满足您为特定monad证明的三个属性:百万千克1
将包装好的
t 送入函数与将未包装的t 传入函数相同。正式名称:
feed(f, wrap(x)) = f(x) 。百万千克1百万千克1
把
M t 喂入wrap 对M 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的定义不同,后者依次由
map 和flatten 定义。尽管它们在某些映射下是等价的。这个演示非常好: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 -> String 和f :: String -> Bool 。我们可以做
(f . g) x ,这和f (g x) 是一样的,其中x 是Int 值。当进行组合/将一个函数的结果应用到另一个函数时,类型匹配非常重要。在上述情况下,
g 返回的结果类型必须与f 接受的类型相同。但有时值在上下文中,这使得排列类型变得不那么容易。(在上下文中拥有值是非常有用的。例如,
Maybe Int 类型表示可能不存在的Int 值,IO String 类型表示由于执行一些副作用而存在的String 值。)假设我们现在有
g1 :: Int -> Maybe String 和f1 :: String -> Maybe Bool 。g1 和f1 分别与g 和f 非常相似。我们不能做
(f1 . g1) x 或f1 (g1 x) ,其中x 是Int 值。g1 返回的结果类型不是f1 预期的类型。我们可以用
. 操作符组合f 和g ,但现在不能用f1 和g1 组合. 。问题是我们不能直接将上下文中的值传递给期望值不在上下文中的函数。如果我们引入一个操作符来组成
g1 和f1 ,这样我们就可以编写(f1 OPERATOR g1) x ,这不是很好吗?g1 返回上下文中的值。该值将脱离上下文并应用于f1 。是的,我们有这样一个接线员。是<=< 。我们还有一个
>>= 操作符,它对我们的作用完全相同,不过语法略有不同。我们写信给:
g1 x >>= f1 。g1 x 是一个Maybe Int 值。>>= 运算符帮助将Int 值从"可能不存在"上下文中去掉,并将其应用于f1 中。f1 是Maybe Bool 的结果,是整个>>= 操作的结果。最后,为什么
Monad 有用?因为Monad 是定义>>= 运算符的类型类,与定义== 和/= 运算符的Eq 类型类非常相似。综上所述,
Monad 类型类定义了>>= 运算符,它允许我们将上下文中的值(我们称为这些单值)传递给不期望上下文中的值的函数。上下文将得到处理。如果这里有一件事需要记住,那就是
Monad 允许在上下文中包含值的函数组合。
型http://code.google.com/p/monad-tutorial/是一个正在进行中的工作,以解决这个问题。
世界需要的是另一篇Monad博客文章,但我认为这对于识别野外现存的Monad很有用。
- 单子是分形的
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.
博士
1
2
3
4
5
6
7
8
9
10序言
应用算子函数
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是结合性的,而
id 是它的权利和左侧的身份。okay.克莱斯利三部曲
在编程中,函子型结构体是一个实例的模子型类。定义与实现有几种等效变量,每一种对Monad抽象的直觉略有不同。
okay.
一种函子是一种类型的结构体
f ,一种类型的* -> * 的函子类型。okay.
1
2
3
4另外,跟踪静态执行型协议,函子类型类的实例必须服从代数函子法EDOCX1〕〔10〕
okay.
1
2函子计算有类型
okay.
1在
c 的背景下,由r 的结果组成。okay.
一种莫纳迪克函数或克莱斯利箭头有这种类型
okay.
1Kleisi arrows are functions that take one regulation
a and return a monadic computationm b .okay.
莫纳德是克莱斯利三重埃多克斯1&16术语的典型定义。
okay.
1作为类型类别实现
okay.
1
2
3
4
5克莱斯利人的身份是一种克莱斯利箭,它促进了在摩纳哥的环境中的EDOCX1价值。应用克莱斯利箭头
a -> m b 来计算m a 。okay.
克莱斯利成分EDOCX1&23
okay.
1
2
3
4复合两个Kleisli箭头,应用左箭头得出右箭头的应用结果。
okay.
Monad种类的实例必须遵循Monad Laws,在Kleisli组成术语中最优雅的说明:EDOCX1&5)
okay.
1
2
3是结合性的,而EDOCX1[…]是它的权利和左侧的身份。
okay.身份
身份类型
okay.
1 type Id t = t类型上的身份功能
okay.
1 Id :: * -> *作为函子解释
okay.
1
2
3
4
5
6
7
8在Canonical Haskell,The identity monad is defined
okay.
1
2
3
4
5
6
7
8
9
10
11
12选项
选择类型
okay.
1计算可能不产生
t 的结果。选择货币okay.
1
2
3
4
5
6
7
8
9
10
11
12只有当
Maybe a 产生结果时才适用。okay.
1自然数可以被编码为这些积分大于或等于零。
okay.
自然数未在亚牵引下关闭。
okay.
1
2
3
4选项Monad covers a basic form of exception handling.
okay.
列表
列表货币,在列表类型上
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扩展将
++ 所有结果列表[b] 从Kleisli箭头a -> [b] 的应用程序f x 连接到[a] 的元素,连接到单个结果列表[b] 。好的。设正整数
n 的适当除数为好的。
1
2
3
4
5然后好的。
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为了简单起见,此解释使用类型类层次结构好的。
在haskell中,当前的标准层次结构是好的。
因为不仅每个单子都是一个函数,而且每个应用程序都是一个函数,每个单子也都是一个应用程序。好的。
使用列表monad,命令式伪代码好的。
1
2
3
4
5大致翻译为do块好的。
等效单子理解好的。
1以及表达好的。
对于嵌套的绑定表达式来说,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定义了保护功能好的。
其中单位类型或"空元组"好的。
1 data () = ()支持选择和失败的加法单元可以通过使用类型类抽象出来。好的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15其中
fail 和<|> 形成单体forall k l m. 。好的。
fail 是加性单体的吸收/湮灭零元素。好的。如果在好的。
even p 为真,则防护产生[()] ,根据>> 的定义,局部常数函数好的。
1应用于结果
() 。如果为假,则警卫会生成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状态访问由原语
get 和put 提供,状态单子上的抽象方法:好的。
1
2
3
4
5 {-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies #-}
class Monad m => Stateful m st | m -> st where
get :: m st
put :: st -> m ()
m -> st 建议功能的状态依赖型st 在单子m ;State 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在
s0 :: Int 同样可靠,是透明的,但更多的优雅和实用infinitely
modify (+ 1) 是计算State Int () 型,除了其对return () 效应等效。该法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输入/输出
1 data World《世界的状态处理器的I / O是一个和解的Haskell单子纯粹和真实的世界,一denotative和操作语义的功能。封闭当前模拟的严格执行:
1的相互作用是通过提高primitives不纯
1
2
3
4
5
6
7该代码是用
IO 杂质是一个永久的primitives按照治疗方案的类型系统。因为性是可怕的,什么发生在IO ,staysIO 中。
1或者,至少,应该的。
Haskell程序的类型的签名)。
扩大到
1 World -> ((), World)该功能是变换的世界。
epilogue
在Haskell的类型和对象类是送来的Haskell函数之间的态射是送来的类型是"快速和宽松"的范畴,
Hask 。
t 函子的范畴是从C 映射到类中的每个对象的C D ;对象在一D
1
2 Tobj : Obj(C) -> Obj(D)
f :: * -> *在与每个在其morphism
C morphismD
1
2在
X ,Y 是C 对象中。HomC(X, Y) 是同态的态射类中的所有X -> Y C 。的身份和morphism函子保持必须的成分,"结构"中的C ,D 。本研究kleisli类类
C 给出由三kleisli
1 <T, eta, _*>一endofunctor
1 T : C -> C(一
f morphismeta (身份),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其中,应用等价转换好的。
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
8monads也可以定义为自然转换
mu ,而不是kleislian扩展,在名为join 的编程中。单胞菌在mu 的术语中被定义为内函数类C 的三倍。好的。
1
2 T : C -> C
f :: * -> *两种自然变形好的。
1
2
3
4
5满足同等条件好的。
1
2
3
4
5然后定义monad类型类好的。
期权Monad的规范
mu 实施:好的。
concat 函数好的。是单子列表中的
join 。好的。
1
2
3
4
5
6
join 的实现可以使用等价物从扩展形式转换而来。好的。从
mu 到扩展形式的反向转换由下式给出:好的。
1
2
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之外世界的变化状态——这使得理解程序的整体效果变得容易得多。
型公主对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似乎就像解释控制流语句。假设一个非程序员要求你解释它们?
你可以给他们一个涉及理论的解释-布尔逻辑,寄存器值,指针,堆栈和帧。但那太疯狂了。
你可以用句法来解释它们。基本上,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一个非常简单的答案是:
monad是一种抽象,它为封装值、计算新的封装值和解包封装值提供了接口。
在实践中,它们的便利之处在于,它们提供了一个统一的接口,用于创建建模状态而非状态的数据类型。
重要的是要理解monad是一种抽象,即处理某种数据结构的抽象接口。然后,该接口用于构建具有单态行为的数据类型。
您可以在Ruby的Monads中找到一个非常好和实用的介绍,第1部分:介绍。
实际上,monad允许回调嵌套(具有相互递归的线程状态(请注意连字符)(以可组合(或可分解)的方式)(具有类型安全性(有时(取决于语言))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))
例如,这不是单子:
1
2
3
4
5
6
7
8
9
10但是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或者把它们塞回一起:
1
2
3
4
5这些能力的实用性并没有真正显现出来,或者在你试图解决真正棘手的问题之前保持清醒比如解析,或者模块/Ajax/资源加载。
你能想象成千上万行的indexof/substring逻辑吗?如果频繁的解析步骤包含在小函数中怎么办?像
chars 、spaces 、upperChars 或digits 这样的功能?如果这些函数在回调中给出了结果,不必与regex组和arguments.slice混淆?如果他们的组成/分解被很好地理解呢?这样你就可以自下而上构建大型解析器了?所以管理嵌套回调范围的能力是非常实用的,尤其是在使用单语法分析器组合器库时。(也就是说,根据我的经验)
不要挂断电话:-范畴论- Maybe单子-单子定律-哈斯克尔-!!!!!
下面是一些"
{| a |m} "代表一片monadic日期。一个日期型advertisesa :
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有个单子样将以下行为的实例…………………
其中,如果是
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}也许是我们的计算,如果它继续单子,其实是包含在
a aborts 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 通,这将对下f f 凸轮表,虽然只是一a 所要求的。在Reader r State st 单子是同类的,虽然它f r 只让看。这一点在所有的是,任何类型的数据,这是符合其本身是一declaring单子的一些排序)中提取价值从周围的单子。大增益从所有这一切?嗯,它的易用性有足够之沙发排序计算的上下文。它可以把凌乱的,然而,当本拉登在stringing多上下文的计算。在照顾解决单子业务上下文的相互作用(这样的程序员是不是必须的。
注意,使用的
>>= eases)由制造一些混乱的f 自主的离开。这是的情况下,在上述的Nothing 例如,f 不再能决定该怎么做的情况下,在Nothing >>= ;它的编码。这是权衡。如果它是必要的适当的f 决定该怎么做的情况下,应该有一个f Nothing ,然后从Maybe a Maybe b 函数)。在这个案例,是一Maybe 单子被失去。注意,然而,有时在没有数据类型的构造函数(口*,看着你,如果你想),我们的工作与我们的小,但有价值的选择上与它的工作monadic接口。
另一个解释monads的尝试,只使用python列表和
map 函数。我完全接受这不是一个完整的解释,但我希望它能成为核心概念。我从Monads上的funfunfunction视频中得到了这个基础,并且学习了haskell章节"更多的Monads"。我强烈推荐观看FunfunFunction视频。
最简单的是,monad是具有
map 和flatMap 功能的对象(haskell中的bind )。有一些额外的必需属性,但这些是核心属性。
flatMap 将映射的输出"展平",对于列表,这只是连接列表的值,例如。
1所以在python中,我们基本上可以用这两个函数实现monad:
func 是接受一个值并返回一个列表的任何函数,例如
1 lambda x: [x*x]解释
为了清晰起见,我通过一个简单的函数在python中创建了
concat 函数,它对列表进行了汇总,即[] + [1] + [4] + [9] = [1, 4, 9] (haskell有一个本机concat 方法)。我假设您知道
map 函数是什么,例如:
1
2展平是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具有以下定义:
即:
return 有三个函数(在大多数其他语言中不与返回混淆)、>>= 和fail 。希望您能看到以下两者的相似之处:
还有:
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的一般类型是:
1bind(
>>= 操作符)只是fmap 和join 的组合,即(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" asx 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数学思维
简而言之:用于组合计算的代数结构。
return data :创建一个只在monad世界中生成数据的计算。
(return data) >>= (return func) :第二个参数接受第一个参数作为数据生成器,并创建连接它们的新计算。您可以认为(>>=)和返回本身不会进行任何计算。它们只是简单地组合和创建计算。
任何单元计算都将在且仅当主函数触发时进行计算。
如果你要求对如此抽象的东西做一个简洁、实用的解释,那么你只能希望得到一个抽象的答案:
1 a -> b是表示从
a s到b s的计算的一种方法。您可以将计算链接起来,也就是将它们组合在一起:
1 (b -> c) -> (a -> b) -> (a -> c)更复杂的计算需要更复杂的类型,例如:
1 a -> f b是从
a s到b s的计算类型,这些计算是进入f s的。您还可以组合它们:
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")运算符,它基本上只是fmap 和join 相继出现。具体地说,好的。注意,该函数出现在第二个参数中,与
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!这使得行为变得相当不可预测——也使得类型系统不那么"纯粹",因为所有函数都采用a 和IO a 值。好的。输入
IO monad。好的。要将操作串在一起,只需扁平嵌套的操作。为了将函数应用于IO操作的输出,在
IO a 类型中的a 只需使用(>>=) 即可。好的。例如,输出输入行(输出行是产生IO动作的函数,与EDOCX1的右参数(33)匹配):好的。
这可以用
do 环境更直观地写:好的。本质上,一个
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 >>= g 与f >>= g 相同。只有当您返回的术语在链的早期创建时,它才有用——见上文。好的。型当然,还有其他的单子,否则它不会被称为单子,它会被称为"IO控制"之类的东西。好的。型
例如,列表monad(
Monad [] 通过连接变平—使(>>=) 运算符对列表的所有元素执行函数。这可以看作是"不确定性",其中列表是许多可能的值,monad框架正在进行所有可能的组合。好的。型例如(在GHCI中):好的。型
1
2
3
4
5
6
7
8
9
10因为:好的。型
号
"也许单子"只会使
Nothing 的所有结果无效。也就是说,绑定自动检查函数(a >>= f 是否返回或值(a >>= f 是否为Nothing —然后返回Nothing 。好的。型
1
2
3
4或者更明确地说:好的。型
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。好啊。
解释这是非常简单的,当用C/JAVA术语解释时:
monad是一个接受参数并返回特殊类型的函数。
这个monad返回的特殊类型也称为monad。(单子是1和2的组合)
有一些语法上的甜头可以使调用这个函数和类型转换更容易。
例子
monad对于使函数式编程人员的生活更简单很有用。典型的例子:
Maybe monad有两个参数,一个值和一个函数。如果传递值为null ,则返回null 。否则,它将评估函数。如果我们需要一个特殊的返回类型,我们也会称这个返回类型为Maybe 。一个非常粗略的实现方式如下:
1
2
3
4
5
6
7这在C语言中是非常无用的,因为这种语言缺少使monad有用所需的语法糖。但是monad允许您用函数式编程语言编写更简洁的代码。
程序员经常用链子来调用monad,就像这样:
在本例中,只有当
回答x 和y 都是非null 时,才会调用Add 方法,否则将返回null 。回答最初的问题:monad是一个函数和一个类型。就像一个特殊的
interface 的实现。遵循你简短、简洁、实用的指示:
理解monad最简单的方法是在上下文中应用/组合函数。假设您有两个计算,都可以看作是两个数学函数
f 和g 。
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"