[英]functions as applicative functors (Haskell / LYAH)
Learn You a Haskell的第 11 章介绍了以下定义:
instance Applicative ((->) r) where
pure x = (\_ -> x)
f <*> g = \x -> f x (g x)
在这里,作者进行了一些不典型的摆手(“<*> 的实例实现有点神秘,所以我们最好只是[展示它的实际效果而不解释它]”)。 我希望这里有人可以帮我弄清楚。
根据应用类定义, (<*>):: f (a -> b) -> fa -> fb
在这个例子中,用((->)r)
代替f
: r->(a->b)->(r->a)->(r->b)
所以第一个问题是,如何从该类型转换为f <*> g = \x -> fx (gx)
?
但即使我认为最后一个公式是理所当然的,我也很难让它与我给 GHCi 的例子一致。 例如:
Prelude Control.Applicative> (pure (+5)) <*> (*3) $ 4
17
这个表达式反而看起来与f <*> g = \x -> f (gx)
一致(请注意,在这个版本中x
没有出现在f
之后。
我意识到这很混乱,所以感谢您的耐心等待。
首先,记住fmap
是如何为应用程序定义的:
fmap f x = pure f <*> x
这意味着您的示例与(fmap (+ 5) (* 3)) 4
相同。 函数的fmap
函数只是组合,因此您的确切表达式与((+ 5). (* 3)) 4
相同。
现在,让我们考虑一下为什么要这样写实例。 <*>
所做的实际上是将函子中的函数应用于函子中的值。 专用于(->) r
,这意味着它将 r 的函数返回的函数应用于r
的函数返回的r
。 返回函数的函数只是两个参数的函数。 所以真正的问题是:如何将两个参数( r
和a
,返回b
)的函数应用到r
的函数返回的值a
?
首先要注意的是,您必须返回类型为(->) r
的值,这意味着结果也必须是r
的函数。 作为参考,这里是<*>
函数:
f <*> g = \x -> f x (g x)
因为我们想要返回一个取值类型为r
的函数,所以x:: r
。 我们返回的函数必须有一个类型r -> b
。 我们如何获得类型b
的值? 好吧,我们有一个函数f:: r -> a -> b
。 由于r
将成为结果函数的参数,我们可以免费获得它。 所以现在我们有一个来自a -> b
的函数。 所以,只要我们有一些 a 类型a
值,我们就可以获得b
类型的值。 但是我们如何获得 a 类型a
值呢? 好吧,我们还有另一个函数g:: r -> a
。 所以我们可以获取类型r
的值(参数x
)并使用它来获取类型a
的值。
所以最终的想法很简单:我们使用参数首先通过将其插入g
来获取类型a
的值。 参数的类型为r
, g
的类型为r -> a
,所以我们有一个a
。 然后,我们将参数和新值都插入到f
中。 我们需要两者,因为f
有一个类型r -> a -> b
。 一旦我们同时插入r
和a
,我们就有了b1
。 由于参数在 lambda 中,结果的类型为r -> b
,这正是我们想要的。
通过您最初的问题,我认为您可能错过了一个微妙但非常关键的点。 使用 LYAH 的原始示例:
(+) <$> (+3) <*> (*100) $ 5
这与:
pure (+) <*> (+3) <*> (*100) $ 5
这里的关键是pure
前(+)
,它具有装箱(+)
作为应用的效果。 如果你看看pure
是如何定义的,你会发现要拆箱,你需要提供一个额外的参数,它可以是任何东西。 将<*>
应用于(+) <$> (+3)
,我们得到
\x -> (pure (+)) x ((+3) x)
注意在(pure (+)) x
中,我们将x
应用于pure
以拆箱(+)
。 所以我们现在有
\x -> (+) ((+3) x)
添加(*100)
得到(+) <$> (+3) <*> (*100)
并再次应用<*>
,我们得到
\y -> (\x -> (+) ((+3) x)) y ((*100) y) {Since f <*> g = f x (g x)}
5 -> (\x -> (+) ((+3) x)) 5 ((*100) 5)
(\x -> (+) ((+3) x)) 5 (500)
5 -> (+) ((+3) 5) (500)
(+) 8 500
508
所以总而言之, f
之后的x
不是我们的二元运算符的第一个参数,它用于对pure
中的运算符进行 UNBOX。
“在这个例子中,用
((->)r)
代替f
:r->(a->b)->(r->a)->(r->b)
”
为什么,那是不对的。 它实际上是(r->(a->b)) -> (r->a) -> (r->b)
,这与(r->a->b) -> (r->a) -> r -> b
。 也就是说,我们将一个中缀和一个返回中缀右侧参数的函数映射到一个只接受中缀 LHS 并返回其结果的函数。 例如,
Prelude Control.Applicative> (:) <*> (\x -> [x]) $ 2
[2,2]
Function as Functor
的Function as Applicative
的 Function?首先,如何理解函数作为函子?
我们可以把仿函数看成一个空盒子如:
instance Functor Maybe where
fmap :: (a -> b) -> f a -> f b
fmap f (Just x) = Just (f x)
fmap f Nothing = Nothing
在那里, Maybe
类型可以被看作是一个带有一个槽的空盒子,它接受一个类型来生成一个具体的类型Maybe a
。 在fmap
函数中:
Maybe
,所以f a是Maybe a
)。 当我们实现函数仿函数时,因为函数仿函数必须有两个参数来构成一个类型a -> b
,如果我们想要我们的函数仿函数恰好有一个槽,我们应该先填充一个槽,所以函数仿函数的类型构造函数是 (( ->) r):
instance Functor ((->) r) where
fmap f g = (\x -> f (g x))
同Maybe
Functor 中的fmap
函数一样,我们应该把第二个参数g看成是f生成的一个具体类型的值( f等于(->) r
),所以f a是(->) ra
可以被视为r -> a
。 最后不难理解, fmap
函数中的g x不能看成是r -> x
,它只是一个函数应用,可以看成(r -> a) x
,也可以看成(x -> a)
.
最后不难理解,Applicative function (->) r
中的<*>函数可以实现如下:
<*> :: f (a -> b) -> f a -> f b
<*> :: (r -> a -> b) -> (r -> a) -> (r -> b)
<&> :: (a -> b) -> (r -> a) -> (r -> b)
f <*> g = \r -> f r (g r)
因为g r会将r映射到a , fra会将r, a映射到b ,所以整个 lambda 函数可以看作r -> b
,也可以看作fb
。 举个例子:
((+) <*> (+3)) 5
结果是 5 + (5 + 3) = 13。
(+) <$> (+3) <*> (*100) $ 5
= 508? 我们知道(+)
有类型: Num a, a -> a -> a
;
我们还知道(+3)
和(*100)
的类型: Num r, a, r -> a
;
(+) <$> (+3)
等于pure (+) <*> (+3)
,其中:t pure (+)
等于Num _, a, _ -> a -> a -> a
换句话说, pure (+)
只是简单地接受一个_
参数并返回+
运算符,参数_
对最终返回值没有影响。 pure (+)
也将函数(+3)
的返回值映射到一个函数。 现在为
f <*> g = \r -> f r (g r)
我们可以应用运算符并得到:
pure (+) <*> (+3) =
\r -> f r (gr) =
\r -> + (gr) =
\r -> + (r + 3) =
\r x -> x + (r + 3)
它的类型为r -> x -> a
。 然后,我们使用 <*> 的定义计算pure (+) <*> (+3) <*> (*100)
,并得到:
pure (+) <*> (+3) <*> (*100) =
\r -> f r (gr) =
\r -> (r + 3) + (gr)
\r -> (r + 3) + (r * 100)
然后我们用参数 5 应用这个函数,我们得到:
(5 + 3) + (5 * 100) = 508
我们可以简单地认为这种应用风格首先计算<$>
之后的值,然后用<$>
之前的运算符将它们相加。 在最后一个例子中,这个运算符是一个二元运算符等于(+)
,我们可以用一个三重运算符(\xyz -> [x,y,z])
代替它,所以下面的等式成立:
(\x y z -> [x,y,z]) <$> (+3) <*> (*2) <*> (/2) $ 5 = [8.0,10.0,2.5]
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.