另外两个答案给出了如何计算的细节 - 但我想我可能会用一个更“直观”的答案来解释如何在不经过详细计算的情况下,人们可以“看到”结果必须是 508 。
正如你所暗示的,每一个Applicative
(事实上,即使每个Functor
)可以被视为一种特定类型的“上下文”,它保存给定类型的值。举个简单的例子:
-
Maybe a
是一个上下文,其中类型的值a
可能存在,但也可能不存在(通常是由于某种原因可能失败的计算结果)
-
[a]
是一个可以保存零个或多个类型值的上下文a
,数量没有上限 - 表示特定计算的所有可能结果
-
IO a
是一个上下文,其中类型的值a
是通过某种方式与“外部世界”互动的结果。 (好吧,这不是那么简单......)
并且,与此示例相关:
-
r -> a
是一个上下文,其中类型的值a
可用,但其特定值尚不清楚,因为它取决于类型的某些(尚未知)值r
.
The Applicative
基于这种背景下的价值观,可以很好地理解方法。pure
将“普通值”嵌入“默认上下文”中,其中它在该上下文中的行为尽可能接近“与上下文无关”的值。我不会对上面 4 个示例中的每一个进行详细介绍(其中大多数都非常明显),但我会注意到对于函数,pure = const
- 即“纯值”a
由总是产生的函数表示a
无论源值是什么。
而不是纠结于如何<*>
虽然可以使用“上下文”隐喻来最好地描述,但我想详细讨论特定的表达方式:
f <$> a <*> b
where f
是 2 个“纯值”之间的函数a
and b
是“上下文中的值”。这个表达式实际上有一个函数的同义词:liftA2 http://hackage.haskell.org/package/base-4.12.0.0/docs/Control-Applicative.html#v:liftA2。虽然使用liftA2
函数通常被认为不如“应用风格”惯用,使用<$>
and <*>
,这个名字强调了这个想法是将“普通价值观”的功能“提升”为“上下文中的价值观”的功能。当这样思考时,我认为在给定特定的“上下文”(即特定的“上下文”)的情况下,它的作用通常是非常直观的Applicative
实例)。
所以表达式:
(+) <$> a <*> b
对于价值观a
and b
类型说f Int
对于一个应用程序f
,对于不同的实例表现如下f
:
- if
f = Maybe
,那么结果,如果a
and b
都是Just
值,是将基础值相加并将它们包装在Just
。如果其中之一a
or b
is Nothing
,那么整个表达式就是Nothing
.
- if
f = []
(列表实例)然后上面的表达式是包含表单的所有总和的列表a' + b'
where a'
is in a
and b'
is in b
.
- if
f = IO
,那么上面的表达式就是一个IO动作,执行了所有的I/O效果a
其次是那些b
,并得出总和Int
s 由这两个动作产生。
那么,最后,如果f
是函数实例吗?自从a
and b
都是描述如何获得给定值的函数Int
给定任意 (Int
)输入,很自然地提升(+)
对它们的函数应该是在给定输入的情况下获得两个结果的函数a
and b
函数,然后将结果相加。
当然,这就是它所做的事情 - 其他答案已经非常巧妙地描绘了它所做的明确路线。但它之所以会这样——事实上,这就是我们有这样的例子的原因f <*> g = \x -> f x (g x)
,这可能看起来相当任意(尽管实际上它是极少数的事情之一,如果不是唯一的,将进行类型检查),以便实例匹配“依赖于某些as-的值”的语义根据给定的函数,还有未知的其他值”。总的来说,我想说,像这样“在高层次上”思考通常比被迫深入了解计算如何执行的低层次细节要好。 (尽管我当然不想淡化能够做到后者的重要性。)
[实际上,从哲学的角度来看,更准确的说法是定义就是这样,因为它是类型检查的“自然”定义,并且实例随后采用这样的方式只是令人高兴的巧合一个美好的“意义”。数学当然充满了这样令人高兴的“巧合”,结果证明它们背后有非常深刻的原因。]