我刚刚从typeclassopedia上读到了Monad
和Applicative
之间的区别。我可以理解Applicative
中没有join
。但是下面的描述在我看来很模糊,我无法弄清楚一元计算/操作的“结果”到底是什么意思。因此,如果我把一个值放入Maybe
中,它就产生了一元。这种“计算”的结果是什么?
我们再仔细看看(〉〉=)的类型,基本的直觉是它把两个计算合并成一个更大的计算,第一个参数m a是第一个计算,但是如果第二个参数只是一个m b,那就太无聊了;那么计算就无法相互交互(实际上,这正是应用型的情况),因此,(〉〉=)的第二个参数的类型为a -〉m b:给定第一次计算的结果,这种类型的函数可以产生要运行的第二次计算。...直观地说,正是这种使用先前计算的输出来决定接下来运行什么计算的能力使得Monad比应用性更强大。应用性计算的结构是固定的,而Monad计算的结构可以基于中间结果而改变。
有没有一个具体的例子来说明“使用前面计算的输出来决定下一次运行什么计算的能力”,而应用程序没有?
7条答案
按热度按时间yx2lnoni1#
我最喜欢的例子是“纯粹应用的Either”。我们将从分析Either的基本Monad示例开始
这个例子嵌入了一个非常自然的短路概念:我们从左到右进行,一旦一个计算“失败”到
Left
中,那么所有其他的计算也会失败。其中
ap
只不过是return
之前的从左到右的排序:现在,当你想收集计算中任何地方出现的错误消息并以某种方式生成错误摘要时,这个
Either
示例的问题就暴露出来了,这与短路背道而驰,也与(>>=)
类型背道而驰如果我们把
m a
看作“过去”,把m b
看作“未来”,那么(>>=)
只要能运行“步进器”(a -> m b)
,就能从过去产生未来。这个“步进器”要求a
的价值确实存在于未来......而这对Either
来说是不可能的。因此(>>=)
* 要求 * 短-电路。因此,我们将实现一个
Applicative
示例,该示例不能有对应的Monad
。现在
(<*>)
的实现是值得仔细考虑的特殊部分,它在前3个例子中执行了一些“短路”,但在第4个例子中做了一些有趣的事情。再次注意,如果我们把左边的参数看作“过去”,右边的参数看作“未来”,那么
(<*>)
与(>>=)
相比是特殊的,因为它可以并行地“打开”未来和过去,而不必为了计算“未来”而需要“过去”的结果。这直接意味着,我们可以使用纯
Applicative
Either
来收集错误,如果链中存在任何Left
,则忽略Right
所以让我们把这个直觉颠倒过来。对于一个纯粹的应用型
Either
,我们有什么不能做的呢?好吧,因为它的操作依赖于在运行过去之前检查未来,所以我们必须能够确定未来的结构,而不依赖于过去的值。换句话说,我们不能写其满足以下等式
我们可以写
ifM
使得
这是不可能的,因为
ifA
完全体现了结果计算的思想,它取决于参数计算中嵌入的值。qybjjes12#
Just 1
描述“计算”,其“结果”是1。Nothing
描述不产生结果的计算。单子和应用式的区别在于单子中有一个选择。单子的关键区别在于在计算中选择不同路径的能力(不仅仅是早期的突破)。根据计算中前一步产生的值,计算结构的其余部分可以改变。
这就是它的意思在一元链中
if
选择要构造什么计算。如果适用,在
所有的函数都在“内部”计算中工作,没有机会打破一个链。每个函数只是转换它所输入的值。从函数的Angular 来看,计算结构的“形状”完全是“外部”的。
一个函数可以返回一个特殊的值来表示失败,但它不能导致计算中的后续步骤被跳过。它们都必须以特殊的方式处理这个特殊的值。计算的形式不能根据接收到的值来改变。
对于单子,函数本身根据自己的选择构造计算。
yftpprvb3#
下面是我对@J. Abrahamson的例子的看法,为什么
ifA
不能使用(pure True)
中的值,本质上,它仍然归结为Applicative
中Monad
缺少join
函数,这统一了类型类中给出的两个不同的视角来解释Monad
和Applicative
之间的区别。因此,使用@J. Abrahamson的纯粹应用型
Either
的例子:(其具有与
Either
Monad
类似的短路效果)和ifA
函数如果我们尝试实现上述方程:
?
好吧,正如已经指出的,最终,
(pure True)
的内容不能被后面的计算使用,但从技术上讲,这是不对的,我们可以使用(pure True)
的内容,因为Monad
也是Functor
与fmap
的组合,我们可以:问题出在
ifA'
的返回类型f (f a)
上,在Applicative
中,无法将两个嵌套的Applicative
S折叠成一个,但这个折叠函数正是join
在Monad
中执行的,因此,将满足
ifA
的方程,如果我们能够适当地实现join
。这里缺少的正是Applicative
函数。换句话说,我们可以使用Applicative
中先前结果的结果。但是在Applicative
框架中这样做将涉及将返回值的类型扩充为嵌套的应用值,我们无法将其恢复为单层应用值。这将是一个严重的问题,因为例如我们无法适当地使用Applicative
S来组合函数。使用join
解决了该问题,但是正是X1 M30 N1 X的引入将X1 M31 N1 X提升为X1 M32 N1 X。k5ifujac4#
差异的关键可以在
ap
的类型与=<<
的类型中观察到。在两种情况下都有
m a
,但只有在第二种情况下m a
才能决定是否应用函数(a -> m b)
。反过来,函数(a -> m b)
可以“决定”是否应用下一个函数界限--通过产生不“包含”b
的m b
(例如[]
、Nothing
或Left
)。在
Applicative
中,m (a -> b)
“内部”的函数无法做出这样的“决定”--它们总是生成b
类型的值。在
Applicative
中这是不可能的,所以不能给出例子。qq24tv8q5#
但是下面的描述在我看来很模糊,我无法弄清楚一元计算/操作的“结果”到底是什么意思。
当然,这种模糊是故意的,因为一元计算的“结果”取决于每种类型,最好的回答有点同义反复:“result”(或 results,因为可能不止一个)是
(>>=) :: Monad m => m a -> (a -> m b) -> m b
示例的实现调用函数参数时使用的任何值。那么,如果我把一个值放入
Maybe
中,它会生成一个单子,那么这个“计算”的结果是什么呢?Maybe
单子如下所示:这里唯一有资格作为“结果”的是
>>=
的第二个等式中的a
,因为它是唯一被“馈入”>>=
的第二个参数的东西。关于
ifA
与ifM
之间的差异,其他答案已经深入讨论过,因此我想强调另一个显著差异:应用合成,单子不合成。对于Monad
s,如果你想创建一个Monad
来组合两个现有的应用,你必须重写其中一个作为单子转换器。相反,如果你有两个Applicatives
,你可以很容易地从它们中创建一个更复杂的应用,如下所示。(代码复制自transformers
。)现在,如果我们加入
Constant
函子/应用式:......我们可以从
Lift
和Constant
的其他响应中组合出“应用型Either
“:bd1hkmkf6#
正如@Will Ness在他的回答中所解释的,关键的区别在于Monads在每一步都有不同执行路径的选择,让我们通过实现一个四次排序的函数来使这个潜在的选择在语法上可见,首先是应用型
f
,然后是monadm
:seq4M
函数在每一步都有一元操作的结果值,因此可以在每一步进行选择,而seq4A
函数只有最后一步的结果值。pgvzfuti7#
我想分享我对这个“iffy miffy”的看法,因为我理解这一切都在上下文得到应用,所以例如:
upps应该只是“真的”...但是
(the“好”的选择是在上下文中做出的)我这样对自己解释,就在计算结束之前,在〉〉1的情况下,我们在“链”中得到类似的东西:
根据适用性的定义,其被评估为:
当“something”是时,根据函子约束(fmap over Nothing给出Nothing),Nothing变为Nothing。并且不可能定义具有“fmap f Nothing = something”的函子。