最近简要介绍了Haskell,对于monad本质上是什么,会有什么简短,简洁,实用的解释?
我发现我遇到的大多数解释都是相当难以接近的,缺乏实际细节。
答案 0 :(得分:1015)
答案 1 :(得分:684)
解释“什么是monad”有点像说“什么是数字?”我们一直使用数字。但想象你遇到了一个对数字一无所知的人。 heck 如何解释数字是什么?你怎么会开始描述为什么这可能有用?
什么是monad?简短的回答:这是将操作链接在一起的一种特定方式。
本质上,您正在编写执行步骤并使用“绑定功能”将它们链接在一起。 (在Haskell中,它的名称为>>=
。)您可以自己编写对绑定运算符的调用,也可以使用语法sugar,使编译器为您插入这些函数调用。但无论哪种方式,每个步骤都通过调用此绑定函数来分隔。
所以bind函数就像一个分号;它分离了一个过程中的步骤。绑定功能的工作是获取上一步的输出,并将其输入下一步。
这听起来不太难,对吗?但是有多种种monad。为什么?怎么样?
好吧,绑定函数可以从一步中获取结果,并将其提供给下一步。但是,如果这是“所有”monad所做的......那实际上并不是非常有用。理解这一点非常重要:每个有用的 monad除了之外还会做的其他事情。每个有用的 monad都具有“特殊能力”,这使其独一无二。
(一个没有特殊的monad被称为“身份monad”。相比身份函数,这听起来像一个完全没有意义的东西,但结果却不是......但那是另一个故事™。)
基本上,每个monad都有自己的bind函数实现。您可以编写一个绑定函数,以便在执行步骤之间进行连接。例如:
如果每个步骤都返回成功/失败指示符,则只有在前一个步骤成功的情况下,才能让绑定执行下一步。这样,失败的步骤将“自动”中止整个序列,而无需您进行任何条件测试。 (失败Monad 。)
扩展这个想法,你可以实现“例外”。 (错误Monad 或 Exception Monad 。)因为您自己定义它们而不是语言功能,所以您可以定义它们的工作方式。 (例如,您可能希望忽略前两个异常,并且仅在抛出第三个异常时中止。)
您可以让每个步骤返回多个结果,并让绑定函数循环遍历它们,将每个步骤送到下一步。这样,在处理多个结果时,您不必在整个地方继续编写循环。绑定功能“自动”为您完成所有这些。 ( List Monad 。)
除了将“结果”从一个步骤传递到另一个步骤之外,您还可以使用绑定功能传递额外数据。此数据现在不会显示在您的源代码中,但您仍然可以从任何地方访问它,而无需手动将其传递给每个函数。 (读者Monad 。)
您可以将其设置为可以替换“额外数据”。这允许您模拟破坏性更新,而无需实际进行破坏性更新。 ( State Monad 及其堂兄作家Monad 。)
因为您只是模拟破坏性更新,所以您可以轻松地执行真正的破坏性更新所无法做到的事情。例如,您可以撤消上次更新,或还原为旧版本。
你可以制作一个可以暂停计算的monad,这样你就可以暂停你的程序,进入并修补内部状态数据,然后恢复它。
< / LI>您可以将“continuation”实现为monad。这可以让你打破人们的思想!
所有这些以及更多都可以使用monad。当然,所有这些也完全可能没有 monad。使用monads只是非常容易 。
答案 2 :(得分:177)
实际上,与莫纳德的共同理解相反,他们与国家无关。 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
),然后说出其return
和bind
操作的工作原理。
很酷的是,事实证明这是一种普遍的模式,它会在整个地方弹出,以纯粹的方式封装状态只是其中之一。
有关monad如何用于引入函数依赖关系并因此控制评估顺序的好文章,就像它在Haskell的IO monad中使用一样,请查看IO Inside。
至于理解monad,不要太担心它。阅读他们您感兴趣的内容,如果您不理解,请不要担心。然后,只需要像Haskell这样的语言潜水就可以了。 Monads是通过练习理解涓涓细流到你的大脑的其中一件事,有一天你突然意识到你理解它们。
答案 3 :(得分:167)
但是,You could have invented Monads!
sigfpe说:但是所有这些都将monad引入了一些需要解释的深奥的东西。但我想说的是,它们根本不是深奥的。事实上,面对函数式编程中的各种问题,你不可避免地会遇到某些解决方案,所有解决方案都是monad的例子。事实上,如果你还没有,我希望你现在可以发明它们。然后,这是一个小步骤,注意到所有这些解决方案实际上都是伪装的相同解决方案。阅读本文之后,你可能会更好地理解monad上的其他文档,因为你会认出你所看到的所有你已经发明的东西。
monad试图解决的许多问题都与副作用问题有关。所以我们将从他们开始。 (注意monads让你做的不仅仅是处理副作用,特别是许多类型的容器对象可以被视为monad。对monad的一些介绍发现很难调和monad的这两种不同用法并且只关注一个或者另一个。)
在诸如C ++这样的命令式编程语言中,函数的行为与数学函数完全不同。例如,假设我们有一个C ++函数,它接受一个浮点参数并返回一个浮点结果。从表面上看,它似乎有点像数学函数映射实数到实数,但C ++函数可以做的不仅仅是返回一个取决于其参数的数字。它可以读取和写入全局变量的值,也可以将输出写入屏幕并接收来自用户的输入。但是,在纯函数式语言中,函数只能读取其参数中提供给它的内容,并且它对世界产生影响的唯一方法是通过它返回的值。
答案 4 :(得分:83)
monad是一种具有两个操作的数据类型:>>=
(aka bind
)和return
(aka unit
)。 return
采用任意值并使用它创建monad的实例。 >>=
获取monad的实例并在其上映射函数。 (您已经可以看到monad是一种奇怪的数据类型,因为在大多数编程语言中,您无法编写一个采用任意值并从中创建类型的函数.Monads使用一种parametric polymorphism。 )
在Haskell表示法中,编写monad接口
class Monad m where
return :: a -> m a
(>>=) :: forall a b . m a -> (a -> m b) -> m b
这些操作应该遵守某些“法律”,但这并不十分重要:“法律”只是编纂了操作的合理实施方式(基本上,>>=
和{{1} }应该同意如何将值转换为monad实例并且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
是Nothing
构造函数。这两个monad都在各自的数据类型中封装了常见且有用的计算模式(请注意,这两种模式都与副作用或I / O无关。)
你真的必须编写一些非平凡的Haskell代码,以了解monad的含义及其有用的原因。
答案 5 :(得分:74)
首先应该了解仿函数是什么。在此之前,请了解更高阶的函数。
高阶函数只是一个函数,它将函数作为参数。
仿函数是任何类型构造T
,其中存在一个高阶函数,称之为map
,它会转换a -> b
类型的函数(将任意两种类型a
和b
)放入函数T a -> T b
中。此map
函数还必须遵守标识和组合法则,以便以下表达式对所有p
和q
(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)
函数,并返回结果列表。
monad 基本上只是一个仿函数T
,有两个额外的方法, join
,类型为T (T a) -> T a
,{类型为unit
的{1}}(有时称为return
,fork
或pure
)。对于Haskell中的列表:
a -> T a
为什么这有用?例如,因为您可以在具有返回列表的函数的列表上join :: [[a]] -> [a]
pure :: a -> [a]
。 map
获取结果列表并将它们连接起来。 Join
是一个单子,因为这是可能的。
您可以编写一个List
,然后map
的函数。此功能称为join
,或bind
,flatMap
或(>>=)
。这通常是在Haskell中给出monad实例的方式。
monad必须满足某些定律,即(=<<)
必须是关联的。这意味着,如果您的join
类型为x
,那么[[[a]]]
应该等于join (join x)
。 join (map join x)
必须是pure
的身份,join
。
答案 6 :(得分:44)
[免责声明:我仍然试图完全修改monad。以下是我到目前为止所理解的内容。如果这是错的,希望知识渊博的人会在地毯上给我打电话。]
Arnar写道:
Monads只是一种包装东西的方法,并提供了对包裹的东西进行操作的方法,而无需将其展开。
就是这样。这个想法是这样的:
您可以使用某些其他信息来包装它。就像值是某种类型(例如整数或字符串)一样,附加信息也是某种类型。
例如,额外信息可能是Maybe
或IO
。
然后你有一些运算符允许你在携带附加信息的同时对包装数据进行操作。这些运算符使用附加信息来决定如何更改包装值的操作行为。
例如,Maybe Int
可以是Just Int
或Nothing
。现在,如果您向Maybe Int
添加Maybe Int
,操作员将检查它们是否都在Just Int
内,如果是,则会打开Int
s ,将结果Int
重新包装到新Just Int
(有效Maybe Int
)中,然后返回Maybe Int
。但是,如果其中一个是Nothing
内部,则此运算符将立即返回Nothing
,这又是有效的Maybe Int
。这样,您可以假装您的Maybe Int
只是正常数字,并对它们进行常规数学运算。如果你得到一个Nothing
,那么你的方程式仍会产生正确的结果 - ,而不必为Nothing
到处进行垃圾检查。
但是,例子就是Maybe
会发生什么。如果额外信息是IO
,则会调用为IO
定义的特殊运算符,并且在执行添加之前可以执行完全不同的操作。 (好吧,将两个IO Int
加在一起可能是荒谬的 - 我还不确定。)(另外,如果你注意Maybe
示例,你已经注意到“用额外的值包装一个值东西“并不总是正确的。但是很难准确,正确和准确,而不是不可理解的。”
基本上,“monad”大致意味着“模式”。但是,您现在拥有语言构造 - 语法和全部 - 而不是一本充满非正式解释和特别命名的模式的书,它允许您将新模式声明为程序中的事物。 (这里的不精确是所有的模式必须遵循一种特定的形式,所以monad并不像模式那样通用。但我认为这是大多数人都知道和理解的最接近的术语。)
这就是人们发现monad如此混乱的原因:因为它们是如此通用的概念。要问是什么使monad成为同样含糊不清的问题是什么使某事成为一种模式。
但是想想在语言中使用句法支持对于模式概念的含义:不必阅读 Gang of Four 书并记住特定模式的构造,你只需要编写以不可知的通用方式实现此模式的代码一次然后就完成了!然后,您可以重复使用此模式,例如Visitor或Strategy或Façade等等,只需通过使用它来修饰代码中的操作,而无需反复重复实现它!
这就是为什么理解 monad的人发现它们有用:这不是一些象牙塔概念,智能势利者自豪于理解(好吧,当然也是如此) ,teehee),但实际上使代码更简单。
答案 7 :(得分:40)
经过多次努力,我想我终于明白了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
)以及一系列专门设计用于该类型的函数,你可以通过定义将这些东西的整个包变成一个“monad”像:
(bind)这样的运算符会自动将其左侧的返回值输入到其右侧的函数参数中,并将lift
运算符转换为可以使用该特定类型的绑定的函数操作
如何实施monad?
看到其他答案,似乎可以自由地深入了解其中的细节。
答案 8 :(得分:34)
(另请参阅 What is a monad? 的答案)
Monads的一个好动机是sigfpe(Dan Piponi)的You Could Have Invented Monads! (And Maybe You Already Have)。有a LOT of other monad tutorials,其中许多错误地试图用“简单术语”用各种类比来解释monad:这是monad tutorial fallacy;避免它们。
正如DR MacIver在 Tell us why your language sucks 中所说:
所以,我讨厌Haskell的事情:
让我们从明显的开始。 Monad教程。不,不是单子。特别是教程。他们是无尽的,夸张的,亲爱的上帝,他们是乏味的。此外,我从未见过任何令人信服的证据证明他们确实有所帮助。阅读类定义,编写一些代码,克服可怕的名字。
你说你明白了可能的monad?好的,你在路上。刚开始使用其他monad,迟早你会明白monad是什么。
[如果你是数学导向的,你可能想忽略几十个教程并学习定义,或者按照lectures in category theory :) 定义的主要部分是Monad M涉及一个“类型构造函数”,它为每个现有类型“T”定义一个新类型“MT”,以及在“常规”类型和“M”之间来回传递的一些方法。类型。]
另外,令人惊讶的是,对monad的最佳介绍之一实际上是介绍monad的早期学术论文之一,Philip Wadler的Monads for functional programming。它实际上具有实用的,非平凡的激励示例,与许多人工教程不同。
答案 9 :(得分:31)
monad实际上是一种“类型操作符”。它会做三件事。首先,它将“包装”(或以其他方式转换)一种类型的值到另一种类型(通常称为“monadic类型”)。其次,它将使基础类型上的所有操作(或函数)在monadic类型上可用。最后,它将支持将自己与另一个monad组合以生成复合monad。
“monad”实际上相当于Visual Basic / C#中的“可空类型”。它采用非可空类型“T”并将其转换为“Nullable&lt; T&gt;”,然后定义所有二元运算符在Nullable&lt; T&gt;上的含义。
副作用以类似方式表示。创建一个结构,其中包含副作用的描述以及函数的返回值。然后,“提升”操作会在函数之间传递值时复制副作用。
它们被称为“monad”,而不是“类型操作符”更容易掌握的名称,原因如下:
答案 10 :(得分:24)
Monads用于控制抽象数据类型对数据的流量。
换句话说,许多开发人员对集合,列表,字典(或哈希,或地图)和树木的想法感到满意。在这些数据类型中有许多特殊情况(例如InsertionOrderPreservingIdentityHashMap)。
然而,当面对程序“流程”时,许多开发人员没有接触到比if,switch / case,do,while,goto(grr)和(可能)闭包更多的构造。
因此,monad只是一个控制流构造。替换monad的更好的短语是“控制类型”。
因此,monad具有用于控制逻辑或语句或函数的插槽 - 数据结构中的等价物可以说某些数据结构允许您添加数据并将其删除。
例如,“if”monad:
if( clause ) then block
最简单的有两个插槽 - 一个子句和一个块。 if
monad通常用于评估子句的结果,如果不是false,则计算块。许多开发人员在学习'if'时并没有被介绍给monad,并且没有必要理解monad来编写有效的逻辑。
Monads可能变得更加复杂,就像数据结构变得更复杂一样,但是monad的许多类别可能具有相似的语义,但实现和语法不同。
当然,就像迭代或遍历数据结构一样,可以评估monad。
编译器可能支持也可能不支持用户定义的monad。 Haskell当然可以。 Ioke有一些类似的功能,虽然monad这个术语并没有在语言中使用。
答案 11 :(得分:14)
我最喜欢的Monad教程:
http://www.haskell.org/haskellwiki/All_About_Monads
(在Google搜索“monad教程”中的170,000次点击中!)
@Stu:monad的观点是允许你将(通常)顺序语义添加到其他纯代码中;你甚至可以编写monad(使用Monad Transformers)并获得更有趣和复杂的组合语义,例如解析错误处理,共享状态和日志记录。所有这些都可以在纯代码中实现,monads只允许你将其抽象出来并在模块库中重复使用(总是很好的编程),并提供方便的语法使其看起来势在必行。
Haskell已经有了运算符重载[1]:它使用类型类,就像在Java或C#中使用接口一样,但Haskell恰好也允许非字母数字标记,如+&amp;&amp;和&gt;作为中缀标识符。如果你的意思是“超载分号”,那么只有运算符重载才能看到它[2]。这听起来像黑魔法并且要求“超出分号”的麻烦(图片进取的Perl黑客得到了这个想法的风)但重点是没有monads 没有分号,因为纯功能代码不需要或允许显式排序。
这一切听起来都比它需要的复杂得多。 sigfpe的文章非常酷,但是使用Haskell来解释它,这种方法无法打破鸡和蛋的问题,理解Haskell来理解Monads并理解Monads以理解Haskell。
[1]这是monad的一个独立问题,但monad使用Haskell的运算符重载功能。
[2]这也是一个过于简单化,因为链接monadic动作的运算符是&gt;&gt; =(发音为“bind”)但是有句法糖(“do”)可以让你使用大括号和分号和/或缩进和换行。
答案 12 :(得分:9)
最近,我一直在以不同的方式思考Monads。我一直认为它们是以数学方式抽象出执行顺序,这使得新的多态性成为可能。
如果您正在使用命令式语言,并且按顺序编写了一些表达式,则代码ALWAYS将按此顺序运行。
在简单的情况下,当你使用monad时,感觉是一样的 - 你定义了一个按顺序发生的表达式列表。除此之外,根据您使用的monad,您的代码可能按顺序运行(如在IO monad中),同时并行运行多个项目(如List monad),它可能会中途停止(如在Maybe monad中) ,它可能会暂停一段时间以便稍后恢复(比如在Resumption monad中),它可能会从头开始倒回并开始(就像在一个Transaction monad中),或者它可能会在中途回放以尝试其他选项(如在Logic monad中)
因为monad是多态的,所以可以根据你的需要在不同的monad中运行相同的代码。
另外,在某些情况下,可以将monad组合在一起(使用monad变换器)以同时获得多个功能。
答案 13 :(得分:9)
我仍然是monads的新手,但我想我会分享一个我觉得非常好读的链接(带图片!!): http://www.matusiak.eu/numerodix/blog/2012/3/11/monads-for-the-layman/ (没有隶属关系)
基本上,我从文章中得到的温暖和模糊的概念是monad基本上是适配器,允许不同的功能以可组合的方式工作,即能够串起多个功能并混合和匹配它们而不用担心关于不一致的返回类型等。因此,当我们尝试制作这些适配器时,BIND功能负责将苹果与苹果和橙子与橙子保持在一起。并且LIFT功能负责采用“低级”功能并“升级”它们以使用BIND功能并且也可以组合。
我希望我做对了,更重要的是,希望这篇文章对monad有一个有效的观点。如果不出意外,这篇文章有助于激发我对学习monad的兴趣。
答案 14 :(得分:8)
除了上面的优秀答案之外,让我为您提供以下文章(Patrick Thomson)的链接,该文章通过将概念与JavaScript库 jQuery (及其方式)相关联来解释monads使用“方法链”来操纵DOM): jQuery is a Monad
jQuery documentation本身并不是指“monad”这个词,而是谈论可能更熟悉的“构建者模式”。这并没有改变你有一个合适的monad甚至没有意识到它的事实。
答案 15 :(得分:8)
Monads Are Not Metaphors,但正如Daniel Spiewak所解释的那样,这是一种从普通模式中产生的实用抽象。
答案 16 :(得分:6)
实际上,monad是函数组合运算符的自定义实现,它负责处理副作用以及不兼容的输入和返回值(用于链接)。
答案 17 :(得分:6)
monad是一种将计算结合在一起的方式,它们共享一个共同的上下文。这就像建立一个管道网络。构建网络时,没有数据流过它。但是当我用'bind'和'return'完成所有位的拼接后,我调用类似runMyMonad monad data
的东西,数据流过管道。
答案 18 :(得分:5)
在了解这些事情时,这两件事对我有所帮助:
Graham Hutton的书Programming in Haskell中的第8章“功能解析器”。实际上,这根本没有提到monad,但是如果你可以完成章节并真正理解其中的所有内容,特别是如何评估一系列绑定操作,你就会理解monad的内部结构。期待这需要几次尝试。
教程All About Monads。这给出了几个很好的例子,我不得不说Appendex中的类比我为我工作。
答案 19 :(得分:5)
这个答案从一个激励性的例子开始,通过这个例子,得到一个monad的例子,并正式定义“monad”。
在伪代码中考虑这三个函数:
f(<x, messages>) := <x, messages "called f. ">
g(<x, messages>) := <x, messages "called g. ">
wrap(x) := <x, "">
f
采用<x, messages>
形式的有序对,并返回有序对。它使第一个项目保持不变,并将"called f. "
附加到第二个项目。与g
相同。
您可以编写这些函数并获取原始值,以及显示函数调用顺序的字符串:
f(g(wrap(x)))
= f(g(<x, "">))
= f(<x, "called g. ">)
= <x, "called g. called f. ">
您不喜欢f
和g
负责将自己的日志消息附加到以前的日志记录信息这一事实。 (想象一下,为了论证而不是追加字符串,f
和g
必须在对的第二项上执行复杂的逻辑。重复这两个复杂的逻辑会很痛苦 - - 或更多 - 不同的功能。)
您更喜欢编写更简单的函数:
f(x) := <x, "called f. ">
g(x) := <x, "called g. ">
wrap(x) := <x, "">
但是看看你写作时会发生什么:
f(g(wrap(x)))
= f(g(<x, "">))
= f(<<x, "">, "called g. ">)
= <<<x, "">, "called g. ">, "called f. ">
问题是将一对传递给一个函数并没有给你你想要的东西。但是,如果你能将一对输入函数,该怎么办:
feed(f, feed(g, wrap(x)))
= feed(f, feed(g, <x, "">))
= feed(f, <x, "called g. ">)
= <x, "called g. called f. ">
将feed(f, m)
视为“Feed m
进入f
”。要提供一对<x, messages>
到一个函数f
,传递 x
到f
,获取{{1}离开<y, message>
,然后返回f
。
<y, messages message>
请注意当您使用函数执行三项操作时会发生什么:
首先:如果你包装一个值,然后将结果对变成一个函数:
feed(f, <x, messages>) := let <y, message> = f(x)
in <y, messages message>
这与将值传递给函数相同。
第二:如果你将一对送入 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
这不会改变这对。
第三:如果你定义一个将 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
并将一对喂入其中:
h(x) := feed(f, g(x))
这与将该对进入 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
相同。
你有大部分的monad。现在您只需要了解程序中的数据类型。
f
是什么类型的值?那么,这取决于值<x, "called f. ">
的类型。如果x
的类型为x
,那么您的货币对的类型为“t
和字符串对”。请拨打t
类型。
M t
是一个类型构造函数:M
单独不引用类型,但M
在您填充类型的空白后引用类型。 M _
是一对int和一个字符串。 M int
是一对字符串和一个字符串。等
恭喜,您创建了一个monad!
正式地,你的monad是元组M string
。
monad是一个元组<M, feed, wrap>
,其中:
<M, feed, wrap>
是一个类型构造函数。M
获取(feed
并返回t
}和M u
的函数,并返回M t
。M u
需要wrap
并返回v
。 M v
,t
和u
是可能相同或不同的三种类型。 monad满足您为特定monad证明的三个属性:
将一个包裹的v
提供到一个函数中与将未包装的t
传递给函数
正式:t
将feed(f, wrap(x)) = f(x)
送入M t
对wrap
无效。
正式:M t
将feed(wrap, m) = m
(称之为M t
)送入
m
传递给t
g
M u
(称之为n
)
g
投放到n
与
相同f
投放到m
g
n
g
投放到n
正式:f
其中feed(h, m) = feed(f, feed(g, m))
通常,h(x) := feed(f, g(x))
称为feed
(Haskell中为AKA bind
),>>=
称为wrap
。
答案 20 :(得分:5)
在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
}
}
E.g。
// 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 in functional programming的定义与Monad in Category Theory的定义不同,http://www.slideshare.net/samthemonad/monad-presentation-scala-as-a-category定义为map
和{ {1}}。虽然它们在某些映射下是等价的。此演示文稿非常好:{{3}}
答案 21 :(得分:5)
Monoid似乎能够确保在Monoid和受支持类型上定义的所有操作始终在Monoid中返回支持的类型。例如,任何数字+任何数字=数字,没有错误。
尽管除法接受两个小数,然后返回一个小数,它将除以零定义为haskell中的无穷大为什么(这恰好是一个小数)...
无论如何,Monads只是一种确保您的操作链以可预测的方式运行的方式,以及一种声称为Num的功能 - > Num,由Num-> Num的另一个函数组成,用x调用不说,发射导弹。
另一方面,如果我们有一个能够发射导弹的功能,我们可以用其他功能组成它也可以发射导弹,因为我们的意图很明确 - 我们想发射导弹 - 但它赢了出于某种奇怪的原因,不要尝试打印“Hello World”。
在Haskell中,main是类型IO()或IO [()],这种干扰是奇怪的,我不会讨论它,但这就是我认为发生的事情:
如果我有main,我希望它做一系列动作,我运行程序的原因是产生效果 - 通常是IO。因此,我可以将IO操作链接在一起,以便 - 执行IO,没有别的。
如果我尝试做一些不“返回IO”的事情,程序会抱怨链条没有流动,或基本上“这与我们试图做什么有关 - 一个IO动作”,它似乎迫使程序员保持他们的思路,不偏离并考虑发射导弹,同时创建排序算法 - 不会流动。
基本上,Monads似乎是编译器的一个提示“嘿,你知道这个函数在这里返回一个数字,它实际上并不总是有效,它有时可以生成一个数字,有时甚至根本没有,只是牢记这一点“。知道这一点,如果你试图断言一个monadic动作,monadic动作可能会作为一个编译时异常说“嘿,这实际上不是一个数字,这可以是一个数字,但你不能假设这一点,做一些事情确保流量可以接受。“这在很大程度上阻止了不可预测的程序行为。
看起来monad不是关于纯度,也不是控制,而是关于维护所有行为可预测和定义的类别的标识,或者不编译。当你被期望做某事时,你什么也做不了,如果你不想做任何事情(可见),你就做不了什么。
我想到Monads的最大原因是 - 去看看程序/ OOP代码,你会注意到你不知道程序的起点,也没有结束,所有你看到的是很多跳跃和一个很多数学,魔术和导弹。您将无法维护它,如果可以,您将花费大量时间将整个程序包裹在整个程序中,然后才能理解它的任何部分,因为此上下文中的模块化基于相互依赖的“部分”代码,其中代码被优化为尽可能相关以保证效率/相互关系。 Monads非常具体,定义明确,并确保程序流程可以分析,并隔离难以分析的部分 - 因为它们本身就是monad。 monad似乎是一个“易于理解的单位,可以预测它的完全理解” - 如果你理解“可能”monad,除了“Maybe”之外它没有任何可能做的事情,这看似微不足道,但在大多数非monadic中代码,一个简单的功能“helloworld”可以发射导弹,什么也不做,或破坏宇宙,甚至扭曲时间 - 我们不知道也不保证它是什么。 Monad保证它是什么。这是非常强大的。
“现实世界”中的所有事物似乎都是单子,因为它受到明确的可观察法则的约束,可防止混淆。这并不意味着我们必须模仿这个对象的所有操作来创建类,而是我们可以简单地说“一个正方形是一个正方形”,只是一个正方形,甚至不是一个矩形也不是一个圆形,并且“一个正方形有区域它的一个现有尺寸的长度乘以它自己。无论你有什么方形,如果它是2D空间中的正方形,它的面积绝对不能是它的长度平方,它几乎是无足轻重的证明。这非常强大,因为我们不需要做出断言来确保我们的世界就是这样,我们只是利用现实的含义来阻止我们的计划脱离轨道。
我几乎可以肯定是错的,但我认为这可以帮助那些人,所以希望它可以帮助某人。
答案 22 :(得分:5)
如果我理解正确,IEnumerable派生于monad。我想知道对于我们这些来自C#世界的人来说,这可能是一个有趣的方法吗?
对于它的价值,这里有一些帮助我的教程的链接(不,我还没有理解monad是什么)。
答案 23 :(得分:4)
我将尝试在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
和==
运算符的/=
类型类非常相似。< / p>
总而言之,Monad
类型类定义了>>=
运算符,它允许我们将上下文中的值(我们称之为monadic值)传递给不期望上下文中的值的函数。上下文将被处理。
如果这里有一件事需要记住,那就是 Monad
允许函数组合涉及上下文中的值。
答案 24 :(得分:4)
世界需要的是另一个monad博客文章,但我认为这对于识别野外现有的monad非常有用。
以上是一个名为Sierpinski三角形的分形,是我唯一能记住的分形。分形是与上面三角形类似的自相似结构,其中部分与整体相似(在这种情况下,正好是父三角形的一半)。
Monads是分形。给定monadic数据结构,可以组合其值以形成数据结构的另一个值。这就是它对编程有用的原因,这就是为什么它在很多情况下都会出现的原因。
答案 25 :(得分:4)
http://code.google.com/p/monad-tutorial/正在努力解决这个问题。
答案 26 :(得分:2)
我也试图了解monad。这是我的版本:
Monads是关于重复性事物的抽象。 首先,monad本身是一个类型化接口(如抽象泛型类),它有两个函数:绑定和返回已定义签名。然后,我们可以基于该抽象monad创建具体的monad,当然还有bind和return的特定实现。另外,绑定和返回必须满足一些不变量,以便能够组合/链接具体的monad。
为什么在创建抽象时使用接口,类型,类和其他工具创建monad概念?因为monad提供更多:他们以一种能够在没有任何样板的情况下编写数据的方式强制重新思考问题。
答案 27 :(得分:2)
使用Python列表和map
函数解释monad的另一种尝试。我完全接受这不是一个完整的解释,但我希望它能得到核心概念。
我从funfunfunction video on Monads和Learn You A Haskell章节'For a Few Monads More'得到了这个基础。我高度建议观看funfunfunction视频。
最简单的说,Monads是具有map
和flatMap
函数(Haskell中为bind
)的对象。 有一些额外的必需属性,但这些是核心属性。
flatMap
'展平'地图的输出,对于列表,这只是连接列表的值,例如
concat([[1], [4], [9]]) = [1, 4, 9]
所以在Python中我们基本上只用这两个函数实现Monad:
def flatMap(func, lst):
return concat(map(func, lst))
def concat(lst):
return sum(lst, [])
func
是任何获取值并返回列表的函数,例如
lambda x: [x*x]
为了清楚起见,我通过simple function在Python中创建了concat
函数,该函数对列表进行求和,即[] + [1] + [4] + [9] = [1, 4, 9]
(Haskell具有本地concat
方法)。
我假设您知道map
功能是什么,例如:
>>> list(map(lambda x: [x*x], [1,2,3]))
[[1], [4], [9]]
展平是Monads的关键概念,对于每个Monad对象,这个展平可以让你获得Monad中包含的值。
现在我们可以致电:
>>> flatMap(lambda x: [x*x], [1,2,3])
[1, 4, 9]
这个lambda取值x并将其放入列表中。 monad可以处理从值到monad类型的任何函数,在这种情况下是一个列表。
这是你的monad定义。
我认为其他问题已经解答了为什么它们有用的问题。
其他不是列表的示例是JavaScript Promises,它具有then
方法,JavaScript Streams具有flatMap
方法。
所以Promise和Streams使用稍微不同的函数来平展Stream或Promise并从内部返回值。
Haskell list monad具有以下定义:
instance Monad [] where
return x = [x]
xs >>= f = concat (map f xs)
fail _ = []
即。有三个函数return
(不要与大多数其他语言的返回相混淆),>>=
(flatMap
)和fail
。
希望你能看到:
之间的相似之处xs >>= f = concat (map f xs)
和
def flatMap(f, xs):
return concat(map(f, xs))
答案 28 :(得分:2)
让下面的“{| a |m}
”代表一些monadic数据。广告a
:
(I got an a!)
/
{| a |m}
功能f
知道如何创建monad,只要它有a
:
(Hi f! What should I be?)
/
(You?. Oh, you'll be /
that data there.) /
/ / (I got a b.)
| -------------- |
| / |
f a |
|--later-> {| b |m}
在这里,我们看到函数f
尝试评估一个monad但遭到斥责。
(Hmm, how do I get that a?)
o (Get lost buddy.
o Wrong type.)
o /
f {| a |m}
功能f
找到了一种使用a
提取>>=
的方法。
(Muaahaha. How you
like me now!?)
(Better.) \
| (Give me that a.)
(Fine, well ok.) |
\ |
{| a |m} >>= f
很少f
知道,monad和>>=
是勾结的。
(Yah got an a for me?)
(Yeah, but hey |
listen. I got |
something to |
tell you first |
...) \ /
| /
{| a |m} >>= f
但他们实际谈的是什么?嗯,这取决于monad。仅仅在摘要中说话的用途有限;你必须有一些特殊的monad经验才能充实理解。
例如,数据类型为Maybe
data Maybe a = Nothing | Just a
有一个monad实例,其行为如下......
其中,如果案件是Just a
(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
(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
,则Maybe monad允许计算继续,但如果不是,则中止计算。然而,结果仍然是一个monadic数据,虽然不是f
的输出。因此,Maybe monad用于表示失败的上下文。
不同的monad表现不同。列表是具有monadic实例的其他类型的数据。它们的行为如下:
(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
,除非它知道最终输出具有相同类型的上下文。
还有其他monad用于表示不同的上下文。这是一些更多的特征。 IO
monad实际上没有a
,但它知道一个人并且会为你获得a
。 State st
monad隐藏着st
,它会传递到表格下的f
,即使f
只是要求a
。 Reader r
monad与State st
类似,但只允许f
查看r
。
所有这一点的要点是,任何类型的数据都被声明为Monad,它声明了从monad中提取值的某种上下文。这一切的巨大收益?嗯,它很容易在某种情况下进行计算。但是,当将多个上下文负载计算串联起来时,它可能会变得混乱。 monad操作负责解决上下文的交互,以便程序员不必这样做。
请注意,使用>>=
可以通过从f
获取一些自治权来减轻混乱。也就是说,在Nothing
的上述情况中,f
不再决定在Nothing
的情况下该做什么;它在>>=
中编码。这是权衡。如果f
有必要决定在Nothing
的情况下该做什么,那么f
应该是Maybe a
到Maybe b
的函数。在这种情况下,Maybe
是一个单子格是无关紧要的。
但是,请注意,有时数据类型不会导出它的构造函数(看着你的IO),如果我们想使用广告的值,我们别无选择,只能使用它的monadic接口。
答案 29 :(得分:2)
基本,实际,monads允许回调嵌套
(具有相互递归线程的状态(请原谅连字符))
(以可组合(或可分解)的方式)
(类型安全(有时(取决于语言)))
)))))))))))))))))))))))))))))))))))))))))))))))))) )))))))))))))))))))))))))))))))))))
E.G。这是不一个monad:
//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...}
。
哪个基本上不重要,实际上不切实际。
现在我可以使用它了:
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)
或者分手:
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);
});});
或忽略某些结果:
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);
});});
或简化一个简单的案例:
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);
});});
To(使用"Right Identity"):
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;
});
或者将它们重新组合在一起:
var getAllThree =
bind(getFirst, function(first_dontCareNow){
return bind(getSecond,function(second_dontCareNow){
return getThird;
});});
这些能力的实用性并没有真正出现, 或者变得清晰直到你试图解决真正棘手的问题 比如解析,或模块/ ajax /资源加载。
你能想象成千上万行的indexOf / subString逻辑吗?
如果在小函数中包含频繁的解析步骤会怎么样?
chars
,spaces
,upperChars
或digits
等函数?
如果这些函数在回调中给你结果,那该怎么办呢?
不必乱用正则表达式组和arguments.slice?
如果他们的成分/分解得到了很好的理解呢?
这样你就可以自下而上构建大型解析器了吗?
因此,管理嵌套回调范围的能力非常实用,
特别是在使用monadic解析器组合库时
(也就是说,根据我的经验)
不要打开电话:
- 类别理论
- MAYBE-MONADS
- MONAD LAWS
- HASKELL
- !!!!
答案 30 :(得分:2)
Princess对F# Computation Expressions的解释对我有所帮助,但我仍然不能说我真的理解。
编辑:这个系列 - 用javascript解释monad - 是为我提供“平衡”的那个。
http://blog.jcoglan.com/2011/03/06/monad-syntax-for-javascript/
http://blog.jcoglan.com/2011/03/11/promises-are-the-monad-of-asynchronous-programming/
我认为理解monad会让你感到沮丧。从这个意义上讲,阅读尽可能多的“教程”是一个好主意,但通常奇怪的东西(不熟悉的语言或语法)会阻止你的大脑集中精力。
我难以理解的一些事情:
a -> M<a>
)和绑定(M<a> -> (a -> M<b>) -> M<b>
)很棒,但我无法理解的是,绑定可以从a
中提取M<a>
以便通过它进入a -> M<b>
。我不认为我曾经读过任何地方(也许对其他人来说都是显而易见的),返回(M<a> -> a
)的反面在中存在 monad,它不需要暴露。答案 31 :(得分:2)
一个非常简单的答案是:
Monads是一个抽象,它提供了一个用于封装值,计算新封装值以及解包封装值的接口。
他们在实践中方便的是他们提供了一个统一的界面来创建模型状态而不是有状态的数据类型。
理解Monad是抽象是很重要的,也就是说,它是一个用于处理某种数据结构的抽象接口。然后,该接口用于构建具有monadic行为的数据类型。
您可以在 Monads in Ruby, Part 1: Introduction 中找到非常实用的介绍。
答案 32 :(得分:2)
解释monad似乎就像解释控制流语句一样。想象一下,非程序员要求你解释它们吗?
你可以给他们一个涉及理论的解释 - 布尔逻辑,寄存器值,指针,堆栈和帧。但那会很疯狂。
您可以根据语法解释它们。基本上C中的所有控制流语句都有大括号,您可以区分条件和条件代码它们相对于括号的位置。这可能更疯狂。
或者您也可以解释循环,if语句,例程,子例程以及可能的协同例程。
Monads可以取代相当多的编程技术。在支持它们的语言中有一种特定的语法,以及关于它们的一些理论。
它们也是函数式程序员使用命令式代码而不实际承认它的一种方式,但这不是他们唯一的用途。
答案 33 :(得分:2)
在Coursera "Principles of Reactive Programming"培训中,Erik Meier将其描述为:
"Monads are return types that guide you through the happy path." -Erik Meijer
答案 34 :(得分:2)
monad是用于封装具有更改状态的对象的东西。它通常在语言中遇到,否则不允许您具有可修改的状态(例如,Haskell)。
一个例子是文件I / O.
您可以使用monad进行文件I / O,以将更改状态的性质与仅使用Monad的代码隔离开来。 Monad中的代码可以有效地忽略Monad之外的世界变化状态 - 这样可以更容易地推断出你的程序的整体效果。
答案 35 :(得分:1)
http://mikehadlow.blogspot.com/2011/02/monads-in-c-8-video-of-my-ddd9-monad.html
这是您要查找的视频。
在C#中演示组合和对齐类型的问题,然后在C#中正确实现它们。 接近最后,他展示了相同的C#代码在F#中的表现以及最终在Haskell中的表现。
答案 36 :(得分:1)
请参阅以下幻灯片,一次尝试从一个角度回答该问题,重点是Scala:
答案 37 :(得分:0)
数学思维
简而言之:用于组合计算的代数结构。
return data
:创建一个只在monad世界中生成数据的计算。
(return data) >>= (return func)
:第二个参数接受第一个参数作为数据生成器,并创建一个连接它们的新计算。
您可以认为(&gt;&gt; =)和返回本身不会进行任何计算。他们只是简单地组合并创建计算。
当且仅当 main 触发它时,任何monad计算都将被计算。
答案 38 :(得分:0)
如果你要求对一些如此抽象的东西进行简洁实用的解释,那么你只能希望得到一个抽象的答案:
a -> b
是表示从a
到b
s的计算的一种方式。您可以将计算链接起来,即撰写它们:
(b -> c) -> (a -> b) -> (a -> c)
更复杂的计算需要更复杂的类型,例如:
a -> f b
是从a
到b
的{{1}} s的计算类型。你也可以撰写它们:
f
事实证明,这种模式几乎无处不在,并且具有与上面第一个组合相同的属性(关联性,右侧和左侧身份)。
一个人不得不给这个模式起一个名字,但是那会有助于知道第一个作品被正式表征为半群吗?
“Monads与括号一样有趣且重要”(Oleg Kiselyov)
答案 39 :(得分:0)
Monad是装有特殊机器的盒子,它使您可以从两个嵌套盒子中制作一个普通盒子-但仍保留两个盒子的某些形状。
具体来说,它允许您执行join
类型的Monad m => m (m a) -> m a
。
它还需要一个return
动作,它只包装一个值。 return :: Monad m => a -> m a
您还可以说join
取消装箱和return
换行-但是join
的类型不是Monad m => m a -> a
的不是(它不会解开所有Monad,用里面的Monad解开Monad。)
因此,它需要一个Monad框(Monad m =>
,m
)和一个内部框((m a)
),并制成一个普通框(m a
)。
但是,通常以(>>=)
(口语为“ bind”)运算符的形式使用Monad,该运算符实际上只是彼此相邻的fmap
和join
。具体来说,
x >>= f = join (fmap f x)
(>>=) :: Monad m => (a -> m b) -> m a -> m b
请注意,该函数位于第二个参数中,而不是fmap
。
也join = (>>= id)
。
现在为什么有用?从本质上讲,它使您可以在使程序在某种框架(Monad)中运行时将动作串在一起。
在Haskell中Monad最突出的用途是IO
Monad。
现在,IO
是在Haskell中对 Action 进行分类的类型。在这里,Monad系统是唯一的保留方式(花哨的单词):
从本质上讲,诸如getLine :: IO String
之类的IO操作无法替换为String,因为它始终具有不同的类型。将IO
视为一种神奇的盒子,可以将东西传送给您。
但是,仍然只是说getLine :: IO String
并且所有函数都接受IO a
会造成混乱,因为可能不需要这些函数。 const "üp§" getLine
会做什么? (const
丢弃了第二个参数。const a b = a
。)getLine
不需要进行求值,但是应该可以进行IO!这使得行为相当不可预测-并使类型系统不太“纯”,因为所有函数都将采用a
和IO a
值。
输入IO
单子。
要将动作串在一起,只需将嵌套动作展平。
要将功能应用于IO操作的输出,即a
类型的IO a
,只需使用(>>=)
。
例如,输出输入的行(输出行是产生IO动作的函数,与>>=
的右参数匹配):
getLine >>= putStrLn :: IO ()
-- putStrLn :: String -> IO ()
这可以在do
环境下更直观地编写:
do line <- getLine
putStrLn line
本质上,像这样的do
块:
do x <- a
y <- b
z <- f x y
w <- g z
h x
k <- h z
l k w
...变成了这个:
a >>= \x ->
b >>= \y ->
f x y >>= \z ->
g z >>= \w ->
h x >>= \_ ->
h z >>= \k ->
l k w
还有>>
的{{1}}运算符(当不需要使用框中的值来在框中创建新框时)
也可以写成m >>= \_ -> f
(a >> b = a >>= const b
)
此外,const a b = a
运算符是根据IO直觉而建模的-它返回具有最小上下文的值,在这种情况下为没有IO 。由于return
中的a
代表返回的类型,因此与命令式编程语言中的IO a
类似-但是它不会停止动作链! return(a)
与f >>= return >>= g
相同。仅当在链中较早时创建了返回的术语时,此功能才有用-见上文。
当然,还有其他Monad,否则将不称为Monad,而将其称为“ IO Control”之类的东西。
例如,List Monad(f >>= g
)通过串联而变平-使Monad []
运算符对列表的所有元素执行功能。这可以看作是“不确定性”,其中列表是许多可能的值,而Monad框架正在将所有可能的组合。
例如(在GHCi中):
(>>=)
因为:
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]
如果发生的话,也许Monad会将所有结果归零到join a = concat a
a >>= f = join (fmap f a)
return a = [a] -- or "= (:[])"
。
也就是说,绑定会自动检查函数(Nothing
a >>=
)返回还是值(f
a
)是>>= f
-然后返回{{ 1}}。
Nothing
或更明确地说:
Nothing
状态Monad用于同时修改某些共享状态-join Nothing = Nothing
join (Just Nothing) = Nothing
join (Just x) = x
a >>= f = join (fmap f a)
的函数,因此Nothing >>= _ = Nothing
(Just x) >>= f = f x
的参数为s -> (a, s)
。
这个名称有点用词不当,因为>>=
实际上是用于状态修改功能,而不是用于状态-状态本身确实没有任何有趣的属性,只是被更改了。
例如:
:: a -> s -> (a, s)
然后:
State
还:
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
答案 40 :(得分:0)
Monad
是Applicative
(即您可以提升二进制的东西-因此,“ n -ary”- (1)函数并将纯值注入(2))Functor
(即您可以映射的东西, (3),即将 unary 函数提升为(3)),具有展平嵌套数据类型的功能。在Haskell中,此展平操作称为join
。
此操作的常规(通用,参数)类型为:
join :: Monad m => m (m a) -> m a
对于任何单子m
(注意所有m
都相同!)。
特定的m
monad定义其特定版本的join
,该版本适用于类型为a
的monadic值“携带”的任何值类型m a
。一些特定的类型是:
join :: [[a]] -> [a] -- for lists, or nondeterministic values
join :: Maybe (Maybe a) -> Maybe a -- for Maybe, or optional values
join :: IO (IO a) -> IO a -- for I/O-produced values
join
操作将产生{em> m
类型值的m
计算的a
计算转换为一个合并的{{1 }}-m
型值的计算。这样可以将计算步骤组合成一个较大的计算。
此计算步骤将“ bind” (a
)运算符组合在一起,简单地将>>=
和fmap
一起使用,即
join
相反,(ma >>= k) == join (fmap k ma)
{-
ma :: m a -- `m`-computation which produces `a`-type values
k :: a -> m b -- create new `m`-computation from an `a`-type value
fmap k ma :: m ( m b ) -- `m`-computation of `m`-computation of `b`-type values
(m >>= k) :: m b -- `m`-computation which produces `b`-type values
-}
可以通过绑定join
定义,其中join mma == mma >>= id
–对于给定类型id ma = ma
而言更方便。
对于monad,使用绑定操作符编写的do表示法及其等效代码,
m
可以读为
首先“执行”
do { x <- mx ; y <- my ; return (f x y) } -- x :: a , mx :: m a -- y :: b , my :: m b mx >>= (\x -> -- nested my >>= (\y -> -- lambda return (f x y) )) -- functions
,完成后,将其“结果”作为mx
,让我用它“执行”其他操作。 >
在给定的x
块中,对于某些类型do
和相同的monad {{,绑定箭头<-
右侧的每个值的类型均为m a
。 1}}在整个a
块中。 m
是中性的do
计算,它仅产生给出的纯值,因此将任何return
计算与之结合不会完全改变该计算。
(1)与m
(2)与m
(3)与liftA2 :: Applicative m => (a -> b -> c) -> m a -> m b -> m c
还有等效的Monad方法,
pure :: Applicative m => a -> m a
给出一个单子,其他定义可以定义为
fmap :: Functor m => (a -> b) -> m a -> m b
答案 41 :(得分:0)
根据What we talk about when we talk about monads,问题是错误的:
“什么是单子论”这个问题的简短答案。是在endofunctors类别中是一个monoid,还是一种具有满足某些定律的两种操作的通用数据类型。这是正确的,但并没有显示出重要的整体情况。这是因为问题是错误的。在本文中,我们旨在回答一个正确的问题,即“作者谈论单子论时真正说些什么?”
尽管该论文没有直接回答什么是单子论,但它有助于了解不同背景的人谈论单子论的含义和原因。
答案 42 :(得分:-1)
用C#/ Java术语解释时非常简单:
monad是一个接受参数并返回特殊类型的函数。
此monad返回的特殊类型是也,称为monad。 (monad是#1和#2的组合)
有一些语法糖来调用这个函数并且类型转换更容易。
monad对于简化函数式程序员的生活非常有用。典型示例:Maybe
monad采用两个参数,一个值和一个函数。如果传递的值为null
,则返回null
。否则它会评估函数。如果我们需要一个特殊的返回类型,我们也会调用此返回类型Maybe
。一个非常粗略的实现看起来像这样:
object Maybe(object value, Func<object,object> function)
{
if(value==null)
return null;
return function(value);
}
这在C#中非常无用,因为这种语言缺乏使monad有用所需的语法糖。但monad允许您在函数式编程语言中编写更简洁的代码。
程序员通常会在链中调用monad,如下所示:
var x = Maybe(x, x2 => Maybe(y, y2 => Add(x2, y2)));
在此示例中,只有Add
和x
都是非y
时才会调用null
方法,否则将返回null
。< / p>
回答原始问题:monad是一个函数和一个类型。就像特殊interface
的实现一样。
答案 43 :(得分:-1)
遵循您简明扼要的实用指示:
理解monad的最简单方法是在上下文中应用/组合函数。假设您有两个计算,它们都可以看作两个数学函数f
和g
。
f
获取一个String并生成另一个String(取前两个字母)g
获取一个String并生成另一个String(大写转换)因此,在任何语言中,转换“取前两个字母并将它们转换为大写”将写成g(f(“some string”))。因此,在纯粹完美功能的世界中,构图只是:做一件事,然后做另一件事。
但是,假设我们生活在可能失败的功能世界中。例如:输入字符串可能是一个char长,因此f将失败。所以在这种情况下
f
接受一个String并生成一个String或Nothing。 g
才会生成字符串。否则,产生Nothing 所以现在,g(f(“some string”))需要一些额外的检查:“Compute f
,如果失败,那么g
应该返回Nothing,否则计算g”
这个想法可以应用于任何参数化类型,如下所示:
让Context [Sometype]计算 Context 中的 Sometype 。考虑功能
f:: AnyType -> Context[Sometype]
g:: Sometype -> Context[AnyOtherType]
组合g(f())应该被称为“计算f。在这个上下文中做一些额外的计算,然后计算g,如果它在上下文中有意义”