可能重复:
什么是单子?
你会如何用非编程术语来描述一个 monad?是否有一些编程之外的概念/事物(在所有编程之外,而不仅仅是 FP)可以说以一种重要的方式行动或像 monad 一样?
可能重复:
什么是单子?
你会如何用非编程术语来描述一个 monad?是否有一些编程之外的概念/事物(在所有编程之外,而不仅仅是 FP)可以说以一种重要的方式行动或像 monad 一样?
是的,在编程之外有几件事可以说是像 monad。不,它们都不会帮助你理解单子。请阅读抽象、直觉和“monad tutorial fallacy”</a>:
Joe Haskeller 正在尝试了解 monad。在努力理解了一周之后,看例子,写代码,读别人写的东西,他终于“啊哈!” 瞬间:一切都变得清晰起来,Joe 了解 Monads!当然,真正发生的事情是乔的大脑已经将所有细节组合成一个更高层次的抽象,乔可以用这个比喻来直观地掌握单子;让我们假设乔的比喻是单子就像墨西哥卷饼。这是乔严重误解了自己的思维过程的地方:“当然!” 乔想。“现在一切都那么简单。理解单子的关键是它们就像墨西哥卷饼。要是我早有这个想法就好了!” 当然,问题在于,如果乔之前想到过这一点,那就无济于事了:
但是现在乔开始编写一个名为“Monads are Burritos”的 monad 教程,这是出于善意但错误的假设,即如果其他人阅读了他的神奇洞察力,对他们来说学习 monad 将是一件轻而易举的事。“单子很容易,”乔写道。“把它们想象成墨西哥卷饼。” Joe 隐藏了关于类型等的所有实际细节,因为它们很可怕,如果人们能避免所有那些困难和令人困惑的东西,他们会学得更好。当然,情况恰恰相反,Joe 所做的只是让人们更难了解 monad,因为现在他们不得不花一周时间认为 monad 是墨西哥卷饼并变得一头雾水,然后一星期试图忘记关于墨西哥卷饼的类比,在他们真正开始学习单子之前。
正如我很久以前在另一个答案中所说,sigfpe 的文章你可以发明 Monads!(也许你已经拥有了。),以及 Philip Wadler 的原始论文Monads for functional programming,都是很好的介绍(没有给出类比,而是提供了很多例子),但除此之外,你只需继续编码,最终一切都会看起来琐碎的。
[不是一个真正的答案:单子存在于所有编程之外的一个地方,当然,是在数学中。正如这篇有趣的帖子所指出的那样,“单子是内函子类别中的一个幺半群,有什么问题?” :-)]
编辑:提问者似乎将这个答案解释为居高临下,说“单子太复杂了,无法类比”。事实上,这种行为并非有意为之,而单子类比往往显得居高临下。也许我应该将我的观点重申为“您不必了解单子”。您使用特定的 monad,因为它们很有用——当您需要 Maybe 类型时,您使用 Maybe monad,当您需要执行 IO 时,您使用 IO monad,其他示例类似,显然在 C# 中,您使用 Nullable<> 模式、LINQ 和查询理解等。现在,对于理解或使用特定的 monad,没有必要理解或使用所有这些结构背后的单一通用抽象,我们称之为 monad。在您看到多个示例并识别出一种模式之后,它可以作为事后的想法出现:学习从具体到抽象。通过诉诸抽象本身的类比来直接解释抽象通常不会帮助学习者掌握它的抽象内容。
这是我目前的尝试:
单子是斗式旅:
>>=
操作)。return
操作只是将东西放入桶中的操作。>>
) 操作的情况下,桶的内容在传递给下一个人之前被转储。下一个人不在乎桶里有什么,他们只是在等待接收。()
,一张票正在桶内传递。它被称为“单位”,它只是一张白纸。希望这可以帮助。:-)
编辑:感谢您的支持,但遗憾的是,Monad 教程的诅咒又来了。我所描述的只是带有容器的函数应用程序,而不是单子!但我不是虚无主义者——我相信 Monad Tutorial 的诅咒可以被打破!所以这里有一个更复杂的图片,我认为它可以更好地描述它。您决定是否值得带给您的朋友。
Monads 是由项目经理组成的水桶大队。项目经理站在除了该旅的第一名成员之外的所有人后面。水桶大队的成员坐在凳子上,前面放着水桶。
第一个人收到一些东西,用它做点什么,然后把它放在桶里。然后那个人放手——不给旅里的下一个人,那太容易了!:-) – 但对站在那个人后面的项目经理。
项目经理(她的名字是bind或>>=
)拿起存储桶并决定如何处理它。她可能决定把第一个人的东西从桶里拿出来,然后毫不犹豫地把它交给她面前的人(这就是 IO monad)。她可能会选择扔掉水桶并结束大队(即fail
)。她可能决定绕过她面前的人,毫不费力地将桶传给旅中的下一位经理(这就是monad中发生的情况)Nothing
。Maybe
她甚至可能决定把桶里的东西拿出来,一次一块地递给她面前的人!(那是 List monad。)在序列 ( >>
) 的情况下,她只是轻拍她面前的人的肩膀,而不是递给他们任何东西。
当下一个人制作一桶东西时,该人将其交给下一位项目经理。下一位项目经理再次弄清楚如何处理她给的桶,并将桶中的东西交给她的人。最后,桶被传递回项目经理链,项目经理可以选择用桶做一些事情(比如List
monad 组装所有结果)。结果,第一个项目经理产生了一桶东西。
在do
语法的情况下,每个人实际上是一个操作,它是在之前的所有内容的上下文中当场定义的——好像项目经理不仅传递存储桶中的内容,还传递值(呃,东西)由该旅的前任成员生成。在这种情况下,如果您使用绑定和序列而不是使用语法来写出计算,那么在这种情况下构建上下文会更容易查看do
——注意每个连续的“语句”都是在该点之前的操作中构造的匿名函数。
() 值、IO monads 和return
操作仍然如上所述。
“可是这也太复杂了!为什么人家不能自己卸桶呢?” 我听到你问。好吧,项目经理可以在幕后做大量工作,否则会使该人的工作复杂化。我们正在努力让这些旅团成员变得轻松,所以他们不必做太多事情。例如,在 Maybe monad 的情况下,每个人都不必检查他们被给予的东西的价值来查看他们是否被给予了任何东西——项目经理会为他们处理这些。
“好吧,那么,如果你真的想让每个人的工作更轻松,为什么不一路走下去——让一个人拿东西和交东西,让项目经理担心分桶?” 经常这样做,并且它有一个特殊的名称,称为将人(er,操作)提升到 monad。但是,有时,你想要一个人做一些更复杂的事情,他们想要对产生的桶进行一些控制(例如Nothing
,在 monad 的情况下他们是否需要返回Maybe
),这就是 monad 的全部内容一般性规定。
要点是:
我的睡前教程就这样结束了。:-P
在非编程方面:
如果F和G 是一对伴随函子,其中F左伴随G,那么组合GF 是一个单子。
是否有一些编程之外的概念/事物(在所有编程之外,而不仅仅是 FP)可以说以一种重要的方式行动或像 monad 一样?
是的,事实上有。通过 Curry-Howard 同构的扩展,Monad 与模态逻辑中的“可能性”直接相关。(参见:模态逻辑的判断重构。)
这是一个相当强的关系,对我来说,逻辑方面与可能性相关的概念比范畴论中与单子相关的概念更直观。我发现向我的学生解释 monad 的最佳方法是利用这种关系,但没有明确显示同构。
基本思想是,没有单子,所有表达式都存在于同一个世界中,所有计算都在那个世界中完成。但是对于单子,可以有很多世界,并且计算在它们之间移动。(例如,每个世界都可能指定某个可变状态的当前值)
在这种观点中,monad 的p
意思是“在从当前世界可能到达的世界中”。
特别是 ift
是一个类型 then :
x :: t
表示 t 类型的东西在当前世界中直接可用
y :: p t
表示 t 类型的东西在从当前世界可达的世界中可用
然后,return
允许我们将当前世界用作可到达的世界。
return :: t -> p t
并>>=
允许我们利用可到达世界中的某物,然后从该世界到达其他世界。
(>>=) :: p t -> (t -> p s) -> p s
所以>>=
可以用来构建一条从通过其他世界的较小路径到可到达世界的路径。
世界就像状态一样,这很容易解释。对于像 IO monad 这样的东西,它也很简单:一个世界由程序与外部世界的所有交互指定。
对于非终结而言,两个世界就足够了——一个普通的世界,一个无限远的未来。(在第二个世界中应用 >>= 是允许的,但您不太可能观察到该世界中发生的情况。)对于延续单子,当正常使用延续时,世界保持不变,并且当它们正常使用时,会有额外的世界不是(例如,对于 callcc)。
从Mike Vanier 的这篇出色的文章中,
Haskell 与其他编程语言不同的关键概念之一是“monad”的概念。人们似乎觉得这很难学(我也学过),因此网络上有大量的 monad 教程,其中一些非常好(我特别喜欢 Jeff Newbern 的 All About Monads)。甚至有人说编写 monad 教程是新 Haskell 程序员的必经之路。然而,许多 monad 教程的一个大问题是他们试图解释 monad 是什么,参考读者已经理解的现有概念(我什至在 GHC 编译器的主要作者 Simon Peyton-Jones 的演讲中看到了这一点和一般的哈斯克尔大便便)。这是一个错误,我将告诉你原因。
当试图解释某事是什么时,通过参考其他人已经知道的事情来解释它是很自然的。当新事物在某些方面与其他人熟悉的事物相似时,这很有效。当新事物完全脱离学习者的经验时,它就会彻底崩溃。例如,如果你试图向一个从未见过火的穴居人解释火是什么,你会怎么说?“这有点像空气和水的混合体,但很热……” 不是很有效。类似地,用量子力学解释原子是什么是有问题的,因为我们知道电子并不是真的像行星围绕恒星一样围绕原子核运行,“离域电子云”的概念并没有多大意义。费曼曾经说过,没有人真正了解量子力学,在直觉层面上这是真的。但在数学层面上,量子力学是众所周知的。我们只是对数学的真正含义没有很好的直觉。
这与单子有什么关系?一次又一次,在教程、博客文章和 Haskell 邮件列表中,我看到 monad 以两种所谓的直观方式之一进行解释:monad 是“有点像一个动作”或“有点像一个容器”。一个东西怎么可能既是一个动作又是一个容器?这些不是独立的概念吗?monad 是某种奇怪的“活动容器”吗?不,但关键是声称 monad 是一种动作或一种容器是不正确的。那么什么是单子呢?
答案是这样的:monad 是一个纯粹抽象的概念,与你以前可能听说过的任何东西都没有基本关系。monad 的概念来自范畴论,这是我所知道的最抽象的数学分支。事实上,范畴论的全部意义在于将数学的所有结构抽象出来,以揭示看似完全不同的领域之间(例如代数和拓扑之间)的相似性和类比性,从而将数学浓缩为它的基本概念,并且从而减少冗余。(我可以继续讨论很长一段时间,但我宁愿回到我试图提出的观点。)因为我猜大多数学习 Haskell 的程序员对范畴论知之甚少,所以 monads对他们没有任何意义。这并不意味着他们需要学习有关范畴论的所有知识才能在 Haskell 中使用单子(幸运的是),
请转到帖子顶部的链接以阅读全文。
在实践中,我使用过的大多数 monad 都表现得像某种隐式上下文。
就像你和一个朋友试图谈论一个共同的朋友一样。每次你说“鲍勃”时,你们都指的是同一个鲍勃,而且由于鲍勃是你们共同的朋友,这一事实只是隐含地贯穿了你们的谈话。
当然,您可以与您的老板(而不是您的朋友)讨论您的跨级别经理(而不是您的朋友),他恰好名叫 Bob。在这里,您可以进行另一次对话,再次带有一些仅在对话上下文中才有意义的隐含含义。您甚至可以说出与您的朋友完全相同的词,但由于上下文不同,它们将具有不同的含义。
在编程中也是如此。行为方式tell
取决于您所在的单子;信息的组合方式(>>=
)取决于您所在的单子。相同的想法,不同的对话方式。
哎呀,即使是谈话的规则也可以是一元的。“不要告诉任何人我告诉过你的事情”隐藏信息的方式与runST
防止引用逃离ST
单子的方式相同。显然,对话可以有一层又一层的上下文,就像我们有一堆单子转换器一样。
希望有帮助。
好吧,这里有一个非常详细的关于 monad的描述,这绝对是所有编程之外的。我知道它超出了编程范围,因为我是一名程序员,我什至不理解它所说的一半。
YouTube 上还有一系列视频解释了各种单子——这是序列中的第一个。
我猜这不是你真正想要的,虽然......
我喜欢将它们视为可以“绑定”的计算的抽象。或者,墨西哥卷饼!
这取决于你在和谁说话。任何解释都必须放在正确的水平上。我对化学工程师的解释不同于我对数学家或财务经理的解释。
最好的方法是将它与您正在与之交谈的人的专业知识联系起来。作为一个规则排序是一个相当普遍的问题,所以试着找到一些人知道你说“先做X,然后做Y”的地方。然后解释一下普通的编程语言是如何出现这个问题的;如果你对计算机说“做 X,然后做 Y”,它会立即做 X 和 Y,而无需等待进一步的输入,但它不能同时为其他人做 Z;计算机的“然后做”的想法与您的不同。因此,程序员必须以不同于您(专家)解释程序的方式编写程序。这会在您所说的内容与程序所说的内容之间产生差距。跨越这个鸿沟需要时间和金钱。
Monads让你把你的“然后做”的版本放到电脑里,所以你可以说“做X然后做Y”,程序员可以写“做{x;y}”,这就是你的意思。
是的,Monads 来自 haskell 之外的一个概念。Haskell 有许多从范畴论中借用的术语和思想。这是其中之一。所以如果这个不是程序员的人原来是一个研究过范畴论的数学家,就说:“Monad 是内函子范畴中的一个幺半群。”