简体   繁体   English

用作应用仿函数(Haskell / LYAH)

[英]functions as applicative functors (Haskell / LYAH)

Chapter 11 of Learn You a Haskell introduces the following definition: Learn You a Haskell的第 11 章介绍了以下定义:

instance Applicative ((->) r) where
    pure x = (\_ -> x)
    f <*> g = \x -> f x (g x)

Here, the author engages in some uncharacteristic hand-waving ("The instance implementation for <*> is a bit cryptic, so it's best if we just [show it in action without explaining it]").在这里,作者进行了一些不典型的摆手(“<*> 的实例实现有点神秘,所以我们最好只是[展示它的实际效果而不解释它]”)。 I'm hoping someone here might help me figure it out.我希望这里有人可以帮我弄清楚。

According to the applicative class definition, (<*>):: f (a -> b) -> fa -> fb根据应用类定义, (<*>):: f (a -> b) -> fa -> fb

In the instance, substituting ((->)r) for f : r->(a->b)->(r->a)->(r->b)在这个例子中,用((->)r)代替fr->(a->b)->(r->a)->(r->b)

So the first question, is how do I get from that type to f <*> g = \x -> fx (gx) ?所以第一个问题是,如何从该类型转换为f <*> g = \x -> fx (gx)

But even if I take that last formula for granted, I have trouble making it agree with examples I give to GHCi.但即使我认为最后一个公式是理所当然的,我也很难让它与我给 GHCi 的例子一致。 For example:例如:

Prelude Control.Applicative> (pure (+5)) <*> (*3) $ 4
17

This expression instead appears consistent with f <*> g = \x -> f (gx) (note that in this version x doesn't appear after f .这个表达式反而看起来与f <*> g = \x -> f (gx)一致(请注意,在这个版本中x没有出现在f之后。

I realize this is messy, so thanks for bearing with me.我意识到这很混乱,所以感谢您的耐心等待。

First of all, remember how fmap is defined for applicatives:首先,记住fmap是如何为应用程序定义的:

fmap f x = pure f <*> x

This means that your example is the same as (fmap (+ 5) (* 3)) 4 .这意味着您的示例与(fmap (+ 5) (* 3)) 4相同。 The fmap function for functions is just composition, so your exact expression is the same as ((+ 5). (* 3)) 4 .函数的fmap函数只是组合,因此您的确切表达式与((+ 5). (* 3)) 4相同。

Now, let's think about why the instance is written the way it is.现在,让我们考虑一下为什么要这样写实例。 What <*> does is essentially apply a function in the functor to a value in the functor. <*>所做的实际上是将函子中的函数应用于函子中的值。 Specializing to (->) r , this means it applies a function returned by a function from r to a value returned by a function from r .专用于(->) r ,这意味着它将 r 的函数返回的函数应用于r的函数返回的r A function that returns a function is just a function of two arguments.返回函数的函数只是两个参数的函数。 So the real question is this: how would you apply a function of two arguments ( r and a , returning b ) to a value a returned by a function from r ?所以真正的问题是:如何将两个参数( ra ,返回b )的函数应用到r的函数返回的值a

The first thing to note is that you have to return a value of type (->) r which means the result also has to be a function from r .首先要注意的是,您必须返回类型为(->) r的值,这意味着结果也必须是r的函数。 For reference, here is the <*> function:作为参考,这里是<*>函数:

f <*> g = \x -> f x (g x)

Since we want to return a function taking a value of type r , x:: r .因为我们想要返回一个取值类型为r的函数,所以x:: r The function we return has to have a type r -> b .我们返回的函数必须有一个类型r -> b How can we get a value of type b ?我们如何获得类型b的值? Well, we have a function f:: r -> a -> b .好吧,我们有一个函数f:: r -> a -> b Since r is going to be the argument of the result function, we get that for free.由于r将成为结果函数的参数,我们可以免费获得它。 So now we have a function from a -> b .所以现在我们有一个来自a -> b的函数。 So, as long as we have some value of type a , we can get a value of type b .所以,只要我们有一些 a 类型a值,我们就可以获得b类型的值。 But how do we get a value of type a ?但是我们如何获得 a 类型a值呢? Well, we have another function g:: r -> a .好吧,我们还有另一个函数g:: r -> a So we can take our value of type r (the parameter x ) and use it to get a value of type a .所以我们可以获取类型r的值(参数x )并使用它来获取类型a的值。

So the final idea is simple: we use the parameter to first get a value of type a by plugging it into g .所以最终的想法很简单:我们使用参数首先通过将其插入g来获取类型a的值。 The parameter has type r , g has type r -> a , so we have an a .参数的类型为rg的类型为r -> a ,所以我们有一个a Then, we plug both the parameter and the new value into f .然后,我们将参数和新值都插入到f中。 We need both because f has a type r -> a -> b .我们需要两者,因为f有一个类型r -> a -> b Once we plug both an r and an a in, we have a b1 .一旦我们同时插入ra ,我们就有了b1 Since the parameter is in a lambda, the result has a type r -> b , which is what we want.由于参数在 lambda 中,结果的类型为r -> b ,这正是我们想要的。

Going through your original question, I think there's one subtle but very key point that you might have missed.通过您最初的问题,我认为您可能错过了一个微妙但非常关键的点。 Using the original example from LYAH:使用 LYAH 的原始示例:

(+) <$> (+3) <*> (*100) $ 5

This is the same as:这与:

pure (+) <*> (+3) <*> (*100) $ 5

The key here is the pure before (+) , which has the effect of boxing (+) as an Applicative.这里的关键是pure(+) ,它具有装箱(+)作为应用的效果。 If you look at how pure is defined, you can see that to unbox it, you need to provide an additional argument, which can be anything.如果你看看pure是如何定义的,你会发现要拆箱,你需要提供一个额外的参数,它可以是任何东西。 Applying <*> to (+) <$> (+3) , we get<*>应用于(+) <$> (+3) ,我们得到

\x -> (pure (+)) x ((+3) x)

Notice in (pure (+)) x , we are applying x to pure to unbox (+) .注意在(pure (+)) x中,我们将x应用于pure以拆箱(+) So we now have所以我们现在有

\x -> (+) ((+3) x)

Adding (*100) to get (+) <$> (+3) <*> (*100) and apply <*> again, we get添加(*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

So in conclusion, the x after f is NOT the first argument to our binary operator, it is used to UNBOX the operator inside pure .所以总而言之, f之后的x不是我们的二元运算符的第一个参数,它用于对pure中的运算符进行 UNBOX。

“In the instance, substituting ((->)r) for f : r->(a->b)->(r->a)->(r->b) “在这个例子中,用((->)r)代替f : r->(a->b)->(r->a)->(r->b)

Why, that's not right.为什么,那是不对的。 It's actually (r->(a->b)) -> (r->a) -> (r->b) , and that is the same as (r->a->b) -> (r->a) -> r -> b .它实际上是(r->(a->b)) -> (r->a) -> (r->b) ,这与(r->a->b) -> (r->a) -> r -> b Ie, we map an infix and a function which returns the infix' right-hand argument, to a function which takes just the infix' LHS and returns its result.也就是说,我们将一个中缀和一个返回中缀右侧参数的函数映射到一个只接受中缀 LHS 并返回其结果的函数。 For example,例如,

Prelude Control.Applicative> (:) <*> (\x -> [x]) $ 2
[2,2]

How to understand Function as Functor and Function as Applicative ?如何理解Function as FunctorFunction as Applicative的 Function?

First, how to understand function as functor?首先,如何理解函数作为函子?

We can regard functor as an empty box such as:我们可以把仿函数看成一个空盒子如:

instance Functor Maybe where 
    fmap :: (a -> b) -> f a -> f b
    fmap f (Just x) = Just (f x) 
    fmap f Nothing = Nothing

there, Maybe type can be seen as an empty box with one slot which take a type to generate a concrete type Maybe a .在那里, Maybe类型可以被看作是一个带有一个槽的空盒子,它接受一个类型来生成一个具体的类型Maybe a In the fmap function:fmap函数中:

  • The first parameter is a function, which maps from a to b;第一个参数是一个函数,从a映射到b;
  • The second parameter is a value of the type with the slot filled(concrete type), this concrete type is generated by type constructor and has the type f a ( f is Maybe , so f a is Maybe a ).第二个参数是填充了槽的类型的值(具体类型),这个具体类型由类型构造函数生成,类型为f afMaybe ,所以f aMaybe a )。

When we implement function functors, for function functors must have two parameters to make a type a -> b , if we want our function functor has exactly one slot, we should first fill a slot, so the type constructor of function functor is ((->) r):当我们实现函数仿函数时,因为函数仿函数必须有两个参数来构成一个类型a -> b ,如果我们想要我们的函数仿函数恰好有一个槽,我们应该先填充一个槽,所以函数仿函数的类型构造函数是 (( ->) r):

instance Functor ((->) r) where 
    fmap f g = (\x -> f (g x))

As the same as the fmap function in Maybe Functor, we should regard the second parameter g as a value of a concrete type which is generate by f ( f equals (->) r ), so f a is (->) ra which can be seen as r -> a .Maybe Functor 中的fmap函数一样,我们应该把第二个参数g看成是f生成的一个具体类型的值( f等于(->) r ),所以f a(->) ra可以被视为r -> a Finally, it is not difficult to understand that the g x in the fmap function cannot be seen as r -> x , it is just a function application which can be seen as (r -> a) x , also (x -> a) .最后不难理解, fmap函数中的g x不能看成是r -> x ,它只是一个函数应用,可以看成(r -> a) x ,也可以看成(x -> a) .

Finally, it is not hard to understand that the <*> function in Applicative function (->) r can be implemented as following:最后不难理解,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)

for g r will map r to a , fra will map r, a to b , so the whole lambda function can be seen as r -> b , also fb .因为g r会将r映射到afra会将r, a映射到b ,所以整个 lambda 函数可以看作r -> b ,也可以看作fb For an instance:举个例子:

((+) <*> (+3)) 5

the result is 5 + (5 + 3) = 13.结果是 5 + (5 + 3) = 13。

How to understand in functions as applicatives, (+) <$> (+3) <*> (*100) $ 5 = 508?如何理解作为应用程序的函数, (+) <$> (+3) <*> (*100) $ 5 = 508?

We know (+) has type: Num a, a -> a -> a ;我们知道(+)有类型: Num a, a -> a -> a ;

We also know (+3) and (*100) has type: Num r, a, r -> a ;我们还知道(+3)(*100)的类型: Num r, a, r -> a

(+) <$> (+3) equals pure (+) <*> (+3) , where :t pure (+) equals Num _, a, _ -> a -> a -> a (+) <$> (+3)等于pure (+) <*> (+3) ,其中:t pure (+)等于Num _, a, _ -> a -> a -> a

In another words, the pure (+) simply takes a _ parameter whatever and return the + operator, the parameter _ has no effect on the final return value.换句话说, pure (+)只是简单地接受一个_参数并返回+运算符,参数_对最终返回值没有影响。 pure (+) also maps the return value of function (+3) to a function. pure (+)也将函数(+3)的返回值映射到一个函数。 Now for现在为

f <*> g = \r -> f r (g r)

we can apply the operators and get:我们可以应用运算符并得到:

pure (+) <*> (+3) = 
    \r -> f r (gr) =
    \r -> + (gr) =
    \r -> + (r + 3) =
    \r x -> x + (r + 3)

it has the type r -> x -> a .它的类型为r -> x -> a We then calculate pure (+) <*> (+3) <*> (*100) using the definition of <*>, and get:然后,我们使用 <*> 的定义计算pure (+) <*> (+3) <*> (*100) ,并得到:

pure (+) <*> (+3) <*> (*100) = 
    \r -> f r (gr) =
    \r -> (r + 3) + (gr)
    \r -> (r + 3) + (r * 100)

then we apply this function with parameter 5, we get:然后我们用参数 5 应用这个函数,我们得到:

(5 + 3) + (5 * 100) = 508 

we can simply think this applicative style as first to calculate the value after <$> and sum them up with the operator before <$> .我们可以简单地认为这种应用风格首先计算<$>之后的值,然后用<$>之前的运算符将它们相加。 In last example, this operator is a binary operator equals (+) , we can replace it with a triple operator (\xyz -> [x,y,z]) , so the following equation holds:在最后一个例子中,这个运算符是一个二元运算符等于(+) ,我们可以用一个三重运算符(\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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM