[英]Applicative functors: why can fmap take a function with more than one argument?
I am getting into Haskell and found the book "learn you a Haskell" most helpful.我正在进入 Haskell 并发现“Learn you a Haskell”一书最有帮助。 I am up to the section on applicative functors .我在applicative functors部分。
I am puzzled by the following as it appears in the book:我对书中出现的以下内容感到困惑:
(\x y z -> [x, y, z]) <$> (+3) <*> (*2) <*> (/2) $ 5
which yields the output:产生输出:
[8.0,10.0,2.5]
First of all, I have confirmed my suspicion in ghci in regards to precedence of the operators, so that the above equals the following ugly statement:首先,我在 ghci 中证实了我对运算符优先级的怀疑,因此上述内容等于以下丑陋的陈述:
(((\x y z -> [x,y,z]) <$> (+3)) <*> (*2) <*> (/2)) $ 5
So from that it becomes clear that the first thing that happens is the fmap
call via the (<$>)
infix operator.因此,很明显,首先发生的是通过(<$>)
缀运算符调用fmap
。
And this is the core of what boggles my mind currently.这是目前让我感到困惑的核心。 The definition of fmap
(here shown as infix (<$>)
) is: fmap
的定义(这里显示为中缀(<$>)
)是:
(<$>) :: (Functor f) => (a -> b) -> f a -> f b
But in the equation I am struggling with, (\\xyz -> [x, y, z])
takes three arguments, not just one.但是在我正在努力解决的等式中, (\\xyz -> [x, y, z])
需要三个参数,而不仅仅是一个。 So how could the first argument of type (a -> b)
be satisfied?那么如何满足(a -> b)
类型的第一个参数呢?
I think it might have to do with partial application / currying but I cannot figure it out.我认为这可能与部分应用/柯里化有关,但我无法弄清楚。 I would greatly appreciate an explanation.我将不胜感激的解释。 Hope I have formulated the question well enough.希望我已经把问题表述得足够好。
Simple answer: there are no functions with multiple arguments in Haskell!简单的回答: Haskell 中没有带有多个参数的函数!
There are two candidates for what you might call "dyadic function": a function that takes a (single!) tuple, and – by far prevalent in Haskell – curried functions .您可能会称之为“二元函数”有两个候选函数:一个接受(单个!)元组的函数,以及——到目前为止在 Haskell 中流行的——柯里化函数。 Those take just one argument, but the result is a function again.那些只需要一个参数,但结果又是一个函数。
So, to figure out what eg fmap (+)
does, let's write所以,为了弄清楚fmap (+)
作用,让我们写
type IntF = Int -> Int
-- (+) :: Int -> IntF
-- fmap :: ( a -> b ) -> f a -> f b
-- e.g.:: (Int->IntF) -> f Int->f IntF
Test it yourself in GHCi:在 GHCi 中自行测试:
Prelude> type IntF = Int -> Int前奏> 输入 IntF = Int -> Int
Prelude> let (#) = (+) :: Int -> IntF前奏> let (#) = (+) :: Int -> IntF
Prelude> :t fmap (#)前奏> :t fmap (#)
fmap (#) :: Functor f => f Int -> f IntF fmap (#) :: Functor f => f Int -> f IntF
Consider a function of type考虑一个类型的函数
f :: a -> b -> c -> d
where d
is any other type.其中d
是任何其他类型。 Due to currying, this can be thought of as a function with the following type由于柯里化,这可以被认为是具有以下类型的函数
f :: a -> (b -> c -> d)
ie a function that takes an a
and returns function of type b -> c -> d
.即一个接受a
并返回类型为b -> c -> d
函数的函数。 If you apply fmap
, you have如果你应用fmap
,你有
-- the type of fmap, which is also :: (a -> r) -> (f a -> f r)
fmap :: Functor f => (a -> r) -> f a -> f r
-- the type of f
f :: a -> (b -> c -> d)
-- so, setting r = b -> c -> d
fmap f :: f a -> f (b -> c -> d)
Which is now of the right type to be used as the left-hand argument to (<*>)
.现在是用作(<*>)
的左侧参数的正确类型。
Because you can take a 3-argument function, feed it just one argument, and this results in a 2-argument function.因为你可以使用一个 3 参数的函数,只给它一个参数,这会产生一个 2 参数的函数。 So you're going to end up with a list of 2-argument functions.所以你最终会得到一个 2 参数函数的列表。 You can then apply one more argument, ending up with a list of 1-argument functions, and finally apply the last argument, whereupon you end up with a list of ordinary numbers.然后你可以再应用一个参数,最后得到一个 1 个参数的函数列表,最后应用最后一个参数,你最终得到一个普通数字的列表。
Incidentally, this is why Haskell has curried functions.顺便说一句,这就是Haskell 具有柯里化函数的原因。 It makes it easy to write constructs like this one which work for any number of function arguments.编写这样的结构很容易,它适用于任意数量的函数参数。 :-) :-)
I personally find the applicative functor instance for functions a bit strange.我个人觉得函数的 applicative functor 实例有点奇怪。 I'll walk you through this example to try to understand intuitively what's going on:我将引导您完成此示例,以尝试直观地了解发生了什么:
>>> :t (\x y z -> [x, y, z]) <$> (+3)
... :: Num a => a -> a -> a -> [a]
>>> ((\x y z -> [x, y, z]) <$> (+3)) 1 2 3
[4,2,3]
This applies (+3)
to the first parameter of the inner function.这适用于(+3)
内部函数的第一个参数。 The other 2 outer parameters are passed to the inner function unmodified.其他 2 个外部参数未经修改地传递给内部函数。
Let's add an applicative:让我们添加一个应用程序:
>>> :t (\x y z -> [x, y, z]) <$> (+3) <*> (*2)
... :: Num a => a -> a -> [a]
>>> ((\x y z -> [x, y, z]) <$> (+3) <*> (*2)) 1 2
[4,2,2]
This applies (+3)
to the first argument as before.这适用于(+3)
如前所述的第一个参数。 With the applicative, the first outer parameter ( 1
) is applied (*2)
and passed as the second parameter of the inner function.对于 applicative,第一个外部参数 ( 1
) 被应用(*2)
并作为内部函数的第二个参数传递。 The second outer parameter is passed unmodified to the inner function as its third parameter.第二个外部参数未经修改地作为第三个参数传递给内部函数。
Guess what happens when we use another applicative:猜猜当我们使用另一个 applicative 时会发生什么:
>>> :t (\x y z -> [x, y, z]) <$> (+3) <*> (*2) <*> (/2)
... :: Fractional a => a -> [a]
>>> (\x y z -> [x, y, z]) <$> (+3) <*> (*2) <*> (/2) $ 1
[4.0,2.0,0.5]
3 applications to the same parameter passed as 3 arguments to the inner function. 3 应用到相同的参数作为 3 个参数传递给内部函数。
It's not theoretically solid explanation, but it can give an intuition about how the applicative instance of functions works.这不是理论上可靠的解释,但它可以直观地说明函数的应用实例是如何工作的。
Let's start with the definition of the <*>
and pure
for functions as an instance of Applicative
.让我们从<*>
和pure
for 函数的定义开始,作为Applicative
一个实例。 For pure
, it will take any garbage value, and return x
.对于pure
,它将采用任何垃圾值,并返回x
。 For <*>
, you can think of it as applying x
to f
, getting a new function out of it, then applying it to the output of gx
.对于<*>
,您可以将其视为将x
应用于f
,从中获取一个新函数,然后将其应用于gx
的输出。
instance Applicative ((->) r) where
pure x = (\_ -> x)
f <*> g = \x -> f x (g x)
Now, let's look at the definition of <$>
.现在,让我们看看<$>
的定义。 It is just an infix version of fmap
.它只是fmap
一个中缀版本。
(<$>) :: (Functor f) => (a -> b) -> f a -> f b
f <$> x = fmap f x
Recall that fmap
has the following implementation:回想一下fmap
有以下实现:
instance Functor ((->) r) where
fmap f g = (\x -> f (g x))
f <$> x
is just pure f <*> x
证明f <$> x
只是pure f <*> x
Let's start with pure f <*> x
.让我们从pure f <*> x
。 Replace pure f
with (\\_ -> f)
.用(\\_ -> f)
替换pure f
。
pure f <*> x
= (\_ -> f) <*> x
Now, let's apply the definition of <*>
, which is f <*> g = \\q -> fq (gq)
.现在,让我们应用<*>
的定义,即f <*> g = \\q -> fq (gq)
。
(\_ -> f) <*> x
= \q -> (\_ -> f) q (x q)
Notice we can simplify (\\_ -> f) q
as just f
.请注意,我们可以将(\\_ -> f) q
简化为f
。 The function takes in whatever value we give it, and returns f
.该函数接受我们给它的任何值,并返回f
。
\q -> (\_ -> f) q (x q)
= \q -> f (x q)
That looks just like our definition of fmap
!这看起来就像我们对fmap
的定义! And the <$>
operator is just infix fmap
.而<$>
运算符只是中缀fmap
。
\q -> f (x q)
= fmap f x
= f <$> x
Let's keep this in mind: f <$> g
is just pure f <*> g
.让我们记住这一点: f <$> g
只是pure f <*> g
。
(\\xyz -> [x, y, z]) <$> (+3) <*> (*2) <*> (/2) $ 5
理解(\\xyz -> [x, y, z]) <$> (+3) <*> (*2) <*> (/2) $ 5
First step is to rewrite the left side of expression to use <*>
instead of <$>
.第一步是重写表达式的左侧以使用<*>
而不是<$>
。 Using what we just proved in in the previous section:使用我们在上一节中刚刚证明的内容:
(\x y z -> [x, y, z]) <$> (+3)
= pure (\x y z -> [x, y, z]) <*> (+3)
So the full expression becomes所以完整的表达式变成
pure (\x y z -> [x, y, z]) <*> (+3) <*> (*2) <*> (/2) $ 5
Let's simplify the first operator using the definition of <*>
让我们使用<*>
的定义简化第一个运算符
pure (\x y z -> [x, y, z]) <*> (+3)
= \a -> f a (g a) --substitute f and g
= \a -> pure (\x y z -> [x, y, z]) a ((+3) a)
Now let's substitute pure x
with (\\_ -> x)
.现在让我们用(\\_ -> x)
替换pure x
。 Observe that a
becomes the garbage value that's used as _
, and is consumed to return the function (\\xyz -> [x, y, z])
.观察到a
成为用作_
的垃圾值,并被消耗以返回函数(\\xyz -> [x, y, z])
。
\a -> (\_-> (\x y z -> [x, y, z])) a ((+3) a)
= \a -> (\x y z -> [x, y, z]) ((+3) a)
Now let's look back at the full expression, and tackle the next <*>
.现在让我们回顾一下完整的表达式,并处理下一个<*>
。 Again, let's apply the definition of <*>
.再次,让我们应用<*>
的定义。
(\a -> (\x y z -> [x, y, z]) ((+3) a)) <*> (*2)
= \b -> (\a -> (\x y z -> [x, y, z]) ((+3) a)) b ((*2) b)
Finally, let's repeat this one last time for the final <*>
.最后,让我们为最后的<*>
重复最后一次。
(\b -> (\a -> (\x y z -> [x, y, z]) ((+3) a)) b ((*2) b)) <*> (/2)
= \c -> (\b -> (\a -> (\x y z -> [x, y, z]) ((+3) a)) b ((*2) b)) c ((/2) c)
Notice that it's a function that takes a single value.请注意,它是一个接受单个值的函数。 We'll feed it 5
.我们会喂它5
。
(\c -> (\b -> (\a -> (\x y z -> [x, y, z]) ((+3) a)) b ((*2) b)) c ((/2) c)) 5
(\5 -> (\b -> (\a -> (\x y z -> [x, y, z]) ((+3) a)) b ((*2) b)) 5 ((/2) 5))
(\b -> (\a -> (\x y z -> [x, y, z]) ((+3) a)) b ((*2) b)) 5 (2.5 )
(\5 -> (\a -> (\x y z -> [x, y, z]) ((+3) a)) 5 ((*2) 5)) (2.5 )
(\a -> (\x y z -> [x, y, z]) ((+3) a)) 5 (10 ) (2.5 )
(\5 -> (\x y z -> [x, y, z]) ((+3) 5)) (10 ) (2.5 )
(\x y z -> [x, y, z]) (8 ) (10 ) (2.5 )
(\x y z -> [x, y, z]) (8) (10) (2.5)
= [8, 10, 2.5]
And that's how we get the final answer.这就是我们得到最终答案的方式。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.