我目前正在阅读《学习Haskell,以获取美好的事物!并为评估某个代码块而费解。我已经阅读了好几次解释,并且开始怀疑甚至连作者都明白这段代码在做什么。
ghci> (+) <$> (+3) <*> (*100) $ 5
508
应用函子在某些上下文中将函数应用于某个上下文中的值,以在某些上下文中获得某些结果。我花了几个小时研究此代码块,并就如何评估此表达式提出了一些解释,但没有一个令人满意。我知道(5 + 3)+(5 * 100)是508,但是问题出在这个表达式上。有人对这段代码有清楚的解释吗?
答案 0 :(得分:6)
它将应用实例用于函数。您的代码
(+) <$> (+3) <*> (*100) $ 5
被评估为
( (\a->b->a+b) <$> (\c->c+3) <*> (\d->d*100) ) 5 ( (\x -> (\a->b->a+b) ((\c->c+3) x)) <*> (\d->d*100) ) 5 ( (\x -> (\a->b->a+b) (x+3)) <*> (\d->d*100) ) 5 ( (\x -> b -> (x+3)+b) <*> (\d->d*100) ) 5 ( (\x->b->(x+3)+b) <*> (\d->d*100) ) 5 (\y -> ((\x->b->(x+3)+b) y) ((\d->d*100) y)) 5 (\y -> (b->(y+3)+b) (y*100)) 5 (\y -> (y+3)+(y*100)) 5 (5+3)+(5*100)
其中<$>
是fmap
或仅仅是函数组合.
,而<*>
是ap
,如果您知道它在单子上的表现的话。
答案 1 :(得分:6)
另外两个答案给出了如何计算的详细信息-但我想我可能会提出一个更“直观”的答案来解释如何无需进行详细的计算就可以“看到”结果必须是508。
正如您所暗示的,每个Applicative
(实际上甚至每个Functor
)都可以被视为一种特定的“上下文”,其中包含给定类型的值。作为简单的例子:
Maybe a
是一个上下文,其中可能存在类型a
的值,但可能不存在(通常是由于某种原因而失败的计算结果)[a]
是一个上下文,可以容纳零个或多个类型a
的值,并且没有上限的数量-表示特定计算的所有可能结果IO a
是一个上下文,其中a
类型的值由于以某种方式与“外界”交互而可用。 (好吧,这不是那么简单...)并且与此示例相关:
r -> a
是一个上下文,其中类型a
的值可用,但是尚不知道其特定值,因为它取决于类型{{1 }}。根据此类上下文中的值,可以很好地理解r
方法。 Applicative
在“默认上下文”中嵌入一个“普通值”,该默认值在该上下文中的行为与“无上下文”的尽可能接近。对于上面的4个示例,我不会逐一介绍(其中大多数都是非常明显的),但是我会注意到,对于函数pure
-即“纯值” pure = const
由无论源值如何总是产生a
的函数表示。
尽管我不想讲究如何使用“上下文”隐喻来最好地描述a
,但我想讲的是特定的表达式:
<*>
其中f <$> a <*> b
是2个“纯值”之间的函数,而f
和a
是“上下文中的值”。该表达式实际上具有一个同义词:liftA2。尽管通常认为使用b
函数比使用liftA2
和<$>
的“应用样式”更惯用,但该名称强调该思想是“提升”一个基于“普通值”的函数到“上下文中的值”。并且当想到这样的时候,我认为通常在给定特定的“上下文”(即特定的<*>
实例)的情况下这样做是非常直观的。
所以表达式:
Applicative
对于适用性(+) <$> a <*> b
,类型为a
的值b
和f Int
的对于不同的实例f
表现如下:
f
,则如果f = Maybe
和a
均为b
值,则结果是将基础值加起来并将它们包装在{{1 }}。如果Just
或Just
为a
,则整个表达式为b
。Nothing
(列表实例),则上面的表达式是一个列表,其中包含形式为Nothing
的所有和,其中f = []
位于a' + b'
和{{1}中}位于a'
中。a
,则上面的表达式是一个IO操作,它执行b'
的所有I / O效果,然后执行b
的I / O效果,并得出{ {1}}是由这两个动作产生的。那么,如果f = IO
是函数实例,那该怎么办?由于a
和b
都是描述如何在给定(Int
)输入的情况下如何获取给定f
的函数,因此自然而然地提升a
函数在它们上面应该是一个函数,该函数在得到输入的情况下会同时获得b
和Int
函数的结果,然后将结果相加。
当然,这就是它的作用-以及其他答案已经很好地勾勒出了它的显式路线。但是它之所以如此工作的原因-实际上,正是我们拥有Int
实例的原因,否则它看起来似乎很武断(尽管实际上这是很少的事情之一,如果不是唯一的话) ,将进行类型检查),以使实例与“根据给定函数依赖于某些尚未知的其他值的值”的语义匹配。总的来说,我想说这样“高层次”思考通常比被迫深入了解如何精确执行计算的低层次细节更好。 (尽管我当然不想低估也能够做到的重要性。)
[实际上,从哲学的角度来看,更确切地说,定义是正确的,只是因为类型检查是“自然的”定义,而实例恰好是巧合,具有如此好的“含义”。当然,数学充满了如此快乐的“巧合”,事实证明它们背后有很深的原因。]
答案 2 :(得分:5)
instance Functor ((->) r) where fmap = (.) instance Applicative ((->) a) where pure = const (<*>) f g x = f x (g x) liftA2 q f g x = q (f x) (g x)
我们要评估的表达式是:
(+) <$> (+3) <*> (*100) $ 5
或更详细:
((+) <$> (+3)) <*> (*100) $ 5
如果我们因此评估(<$>)
(它是fmap
的中缀同义词),则我们看到这等于:
(+) . (+3)
所以这意味着我们的表达式等同于:
((+) . (+3)) <*> (*100) $ 5
接下来,我们可以应用顺序应用。因此,f
等于(+) . (+3)
,g
是(*100)
。因此,这意味着我们构造了一个看起来像这样的函数:
\x -> ((+) . (+3)) x ((*100) x)
我们现在可以简化此过程并将其重写为:
\x -> ((+) (x+3)) ((*100) x)
,然后将其重写为:
\x -> (+) (x+3) ((*100) x)
因此,我们构建了一个看起来像这样的函数:
\x -> (x+3) + 100 * x
或更简单:
\x -> 101 * x + 3
如果我们然后计算:
(\x -> 101*x + 3) 5
那我们当然可以得到:
101 * 5 + 3
因此:
505 + 3
这是预期的:
508
答案 3 :(得分:3)
对于任何应用程序,
a <$> b <*> c = liftA2 a b c
对于功能,
liftA2 a b c x
= a (b x) (c x) -- by definition;
= (a . b) x (c x)
= ((a <$> b) <*> c) x
因此
(+) <$> (+3) <*> (*100) $ 5
=
liftA2 (+) (+3) (*100) 5
=
(+) ((+3) 5) ((*100) 5)
=
(5+3) + (5*100)
(此答案的长版。)
纯数学没有时间。 Pure Haskell没有时间。动词说话(“应用函子应用”等)可能会造成混淆(“ 应用...何时? ...”)。
相反,(<*>)
是一种组合器,它将带有功能(在某些情况下为 )的“计算”(由应用函子表示)组合在一起,将值(在类似上下文中的 )携带到一个组合的“计算”中,该运算将功能应用于该值(在这种情况下的 )。
“计算”用于将其与纯Haskell“计算”进行对比。 “计算”本身可能是纯粹的,也可能不是纯粹的,这是一个正交的问题。但是主要的意思是,“计算”体现了通用函数调用协议。他们可能会在将函数应用到其参数上时,作为/的一部分,来“做”某事。或输入类型,
( $ ) :: (a -> b) -> a -> b
(<$>) :: (a -> b) -> f a -> f b
(<*>) :: f (a -> b) -> f a -> f b
(=<<) :: (a -> f b) -> f a -> f b
使用函数,上下文是应用程序(另一个 一个),并且要恢复值-是函数还是自变量-应用于通用自变量已执行。
(忍受我,我们快到了)。模式a <$> b <*> c
也可以表示为liftA2 a b c
。因此,“函数”的适用函子“计算”类型由
liftA2 h x y s = let x' = x s -- embellished application of h to x and y
y' = y s in -- in context of functions, or Reader
h x' y'
-- liftA2 h x y = let x' = x -- non-embellished application, or Identity
-- y' = y in
-- h x' y'
-- liftA2 h x y s = let (x',s') = x s -- embellished application of h to x and y
-- (y',s'') = y s' in -- in context of
-- (h x' y', s'') -- state-passing computations, or State
-- liftA2 h x y = let (x',w) = x -- embellished application of h to x and y
-- (y',w') = y in -- in context of
-- (h x' y', w++w') -- logging computations, or Writer
-- liftA2 h x y = [h x' y' | -- embellished application of h to x and y
-- x' <- x, -- in context of
-- y' <- y ] -- nondeterministic computations, or List
-- ( and for Monads we define `liftBind h x k =` and replace `y` with `k x'`
-- in the bodies of the above combinators; then liftA2 becomes liftBind: )
-- liftA2 :: (a -> b -> c) -> f a -> f b -> f c
-- liftBind :: (a -> b -> c) -> f a -> (a -> f b) -> f c
-- (>>=) = liftBind (\a b -> b) :: f a -> (a -> f b) -> f b
确实
> :t let liftA2 h x y r = h (x r) (y r) in liftA2
:: (a -> b -> c) -> (t -> a) -> (t -> b) -> (t -> c)
> :t liftA2 -- the built-in one
liftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c
即当我们采用f a ~ (t -> a) ~ (->) t a
,即f ~ (->) t
时,类型匹配。
因此,我们已经到了 :
(+) <$> (+3) <*> (*100) $ 5
=
liftA2 (+) (+3) (*100) 5
=
(+) ((+3) 5) ((*100) 5)
=
(+) (5+3) (5*100)
=
(5+3) + (5*100)
这是为这种类型,liftA2
定义Applicative ((->) t) => ...
的方式:
instance Applicative ((->) t) where
pure x t = x
liftA2 h x y t = h (x t) (y t)
无需定义(<*>)
。 The source code says:
最小完整定义
pure, ((<*>) | liftA2)
所以现在您已经问了很长时间了,为什么 a <$> b <*> c
等同于liftA2 a b c
?
最简单的答案是,就是这样。一个可以用另一个来定义-即(<*>)
可以通过liftA2
定义,
g <*> x = liftA2 id g x -- i.e. (<*>) = liftA2 id = liftA2 ($)
-- (g <*> x) t = liftA2 id g x t
-- = id (g t) (x t)
-- = (id . g) t (x t) -- = (id <$> g <*> x) t
-- = g t (x t)
(与定义in the source完全相同)
h <$> g = pure h <*> g
是每个应用函子都必须遵循的法律。
最后,
liftA2 h g x == pure h <*> g <*> x
-- h g x == (h g) x
因为<*>
位于左侧:infixl 4 <*>
。