haskell 单子连接函数

66bbxpm5  于 2023-01-17  发布在  其他
关注(0)|答案(5)|浏览(133)

虽然单子在Haskell中使用bindreturn函数表示,但是它们也可以使用join函数表示,就像这里讨论的那样,我知道这个函数的类型是M(M(X))->M(X),但是它实际上做什么呢?

rseugnpd

rseugnpd1#

实际上,在某种程度上,join才是所有神奇之处--(>>=)主要是为了方便而使用的。
所有基于Functor的类型类都使用某种类型来描述额外的结构。对于Functor,这种额外的结构通常被认为是一个“容器”,而对于Monad,它往往被认为是“副作用”,但这些只是(偶尔会引起误解)速记--无论哪种方式都是一样的,实际上没有什么特别的**[0]
与其他Functor相比,Monad的显著特点是它可以将 * 控制流 * 嵌入到额外的结构中,其原因是,与fmap不同,fmap在整个结构上应用单个扁平函数,(>>=)检查单个元素并从中构建 * 新 * 结构。
对于普通的Functor,从原始结构的每一部分构建新的结构将替代嵌套Functor,每一层代表一个控制流点,这显然限制了实用性,因为结果是混乱的,并且具有反映所使用的流控制结构的类型。
一元“副作用”是具有一些附加属性的结构
[1]**:

  • 两个副作用可以归为一组(例如,“做X”和“做Y”变为“做X,然后Y”),并且分组的顺序并不重要,只要保持效应的顺序即可。
  • 存在“不采取任何措施”的副作用(例如,“采取X措施”和“不采取任何措施”分组与仅“采取X措施”相同)

join函数只不过是一个分组操作:像m (m a)这样的嵌套单子类型描述了两个副作用以及它们发生的顺序,join将它们组合成一个副作用。
因此,就一元副作用而言,绑定操作是“取一个具有相关副作用的值和一个引入新副作用的函数,然后将该函数应用于该值,同时组合每个副作用”的简写。

**[0]:**除了IO之外,IO * 非常 * 特别。
**[1]:**如果你将这些性质与Monoid的一个示例的规则进行比较,你会发现两者之间有着密切的相似之处--这不是巧合,事实上这就是“只是一个内函子范畴中的幺半群,有什么问题吗?”这一行所指的。

uyhoqukh

uyhoqukh2#

我认为,到目前为止,其他答案已经充分描述了join does 的作用,如果你想得到更直观的理解......如果你想知道join“意味着”什么.,..,.那么不幸的是,答案会根据所讨论的单子而有所不同,特别是M(X)“意味着”什么和M(M(X))“意味着”什么。
如果M是List单子,那么M(M(X))是列表的列表,join的意思是“平坦化”,如果M是Maybe单子,那么M的一个元素(M(X))可以是“刚好(Just x)",“Just Nothing”,或“Nothing”,并且连接意味着以逻辑方式将那些结构折叠为“Just x”,“Nothing”,和“Nothing”(类似于camccann对join合并副作用的回答)。
对于更复杂的单子,M(M(X))变成了一个非常抽象的东西,决定M(M(X))和join“意味着”什么变得更加复杂。在每一种情况下,它都有点像List单子的情况,因为你把单子抽象的两层折叠成一层,但含义会发生变化。对于State单子,camccann结合两个副作用的答案是正确的:join本质上意味着合并两个连续的状态转换。Continuation单子特别令人费解,但数学上join在这里实际上相当简洁:M(X)对应于X的“双重对偶空间”,数学家可能写为X**(延拓本身,即从X-〉R的Map,其中R是最终结果的集合,对应于单对偶空间X*),并且join对应于从X****X**的极其自然的Map。连续单子满足单子定律的事实对应于这样一个数学事实,即应用对偶空间算子*两次以上通常没有多大意义。
但我离题了。
就我个人而言,我尽量克制对单子的所有可能类型应用单一类比的冲动;单子是一个太普遍的概念,不能用一个简单的描述性类比来归类,join的含义会根据你在任何给定时间使用的类比而变化。

qvtsj1bj

qvtsj1bj3#

从同一页我们恢复此信息join x = x >>= id,与bindid函数如何工作的知识,你应该能够找出join做什么。

jgovgodb

jgovgodb4#

从概念上讲,它的作用可以通过查看类型来确定:它打开或展开外部一元容器/计算,并返回其中产生的一元值。
它实际上是如何做到这一点的,这取决于你所处理的单子的种类。例如,对于List单子,'join'等价于concat。

5f0d552i

5f0d552i5#

绑定操作Map:ma -> (a -> mb) -> mb。在ma和(第一个)mb,我们有两个m。根据我的直觉,理解绑定和一元运算在很大程度上取决于理解这两个m(一元上下文的示例)将得到组合。我喜欢把Writer一元看作理解join的一个例子。写入程序可用于记录操作。ma中有一个日志。(a -> mb)将在第一个mb上生成另一个日志。第二个mb将组合这两个日志。
(And一个不好的例子是Maybe单子因为有Just + Just = JustNothing+任何值= Nothing(或F#SomeNone)信息量太小,以至于忽略了正在发生重要事情的事实。您可能倾向于将Just简单地看作是继续执行的单个条件,而将Nothing简单地看作是停止的单个标志。在计算进行时留下。(这是一个合理的印象,因为最终的JustNothing看起来是在计算的最后一步从无到有地从头创建的从前面的转移到它。)当你真的需要在每个场合都专注于Just s和Nothing s的组合学时。)
我在阅读米兰·利波瓦卡的《为了伟大的利益学一门 haskell 》(Learn You a Haskell For Great Good!)第12章,关于单子法则的最后一节时,这个问题变得清晰起来。http://learnyouahaskell.com/a-fistful-of-monads#monad-laws,Associativity.这个要求是:“做(ma >>= f) >>= g就像做ma >>= (\x -> f x >>= g)一样[我用ma来表示m]。”好吧,在两边,这个参数首先传递给f,然后传递给g。那么他说“不容易看出这两个是如何相等的”是什么意思??不容易看出它们是如何不同的!
区别在于m s的join环的结合性(上下文)--这是X1 M31 N1 X ING所做的,Bind打开或绕过m以获得应用fa--但这还不是全部。(在ma上)在f生成第二个m时保持(在mb上)。然后,bind组合--join s--两个mbind的密钥在join中和在展开中一样多(map)。我认为对join的混淆表明了对bind展开方面的关注--从ma中取出a以匹配f参数的签名--而忽略了两个m(从mamb)(丢弃第一个m在某些情况下可能是处理它的适当方法(也许)--但通常情况下并非如此--正如Writer所说明的那样)。
在左边,我们先将bindma转换为f,然后再转换为g("before f" + "after f") + "after g"。在右边,虽然函数fg以相同的顺序应用,但现在我们首先 * 绑定 * 到g。字符串中没有括号,所以两种方式的对数都是相同的,并且遵守了定律(然而,如果第二个对数是"after f" + "after g" + "before f"--那么我们将陷入数学麻烦!)。
对于Writer,将bind重新转换为fmap加上join,得到fmap f ma,其中f:a -> mb,得到m(mb)。将ma上的第一个m视为“f之前”。f应用于第一个m内的a,现在应用于第二个m内的a(或X1 M77 N1 X)到达--在第一个X1 M78 N1 X内,其中Mapf发生。将mb上的第二个m视为“f之后”。m(mb) =(“before f”(“after f”b))。现在我们使用Join来折叠两个日志,m s,生成一个新的m。Writer使用一个幺半群,我们连接起来。其他的幺半群以其他的方式合并上下文--遵守规则。这可能是理解它们的主要部分。

相关问题