46

Learn You a Haskell的第 11 章介绍了以下定义:

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

在这里,作者进行了一些不寻常的挥手(“<*> 的实例实现有点神秘,所以最好只是[在实际中展示它而不解释它]”)。我希望这里有人可以帮我弄清楚。

根据应用类定义,(<*>) :: f (a -> b) -> f a -> f b

在本例中,替换((->)r)fr->(a->b)->(r->a)->(r->b)

所以第一个问题是,我如何从那种类型变为f <*> g = \x -> f x (g x)

但即使我认为最后一个公式是理所当然的,我也很难让它与我给 GHCi 的例子一致。例如:

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

这个表达式反而看起来与f <*> g = \x -> f (g x)(注意在这个版本x中没有出现在f.

我意识到这很混乱,所以感谢您对我的包容。

4

4 回答 4

39

首先,记住如何fmap为应用程序定义:

fmap f x = pure f <*> x

这意味着您的示例与(fmap (+ 5) (* 3)) 4. 函数的fmap函数只是组合,因此您的确切表达式与((+ 5) . (* 3)) 4.

现在,让我们考虑一下为什么要这样编写实例。本质上是<*>将函子中的函数应用于函子中的值。专门用于(->) r,这意味着它将函数 from 返回的函数应用于函数 fromr的返回值r。返回函数的函数只是两个参数的函数。所以真正的问题是:你如何将一个有两个参数(ra,返回b)的函数应用到一个函数a返回的值 from r

首先要注意的是,您必须返回一个类型的值,(->) r这意味着结果也必须是来自r. 作为参考,这里是<*>函数:

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

由于我们要返回一个具有类型值的函数rx :: r. 我们返回的函数必须有一个 type r -> b。我们如何获得 type 的值b?好吧,我们有一个函数f :: r -> a -> b。由于r将成为结果函数的参数,我们免费获得它。所以现在我们有一个函数 from a -> b。所以,只要我们有一些 typea的值,我们就可以得到一个 type 的值b。但是我们如何获得 type 的值a呢?好吧,我们还有另一个功能g :: r -> a。所以我们可以获取 type 的值r(参数x)并使用它来获取 type 的值a

所以最终的想法很简单:我们使用参数首先a通过将其插入到g. 参数有类型rg有类型r -> a,所以我们有一个a. 然后,我们将参数和新值都插入f. 我们需要两者,因为f有一个 type r -> a -> b。一旦我们同时插入 anr和 an ain,我们就有一个b1. 由于参数在 lambda 中,因此结果有一个 type r -> b,这就是我们想要的。

于 2012-08-04T18:28:24.653 回答
31

通过您最初的问题,我认为您可能错过了一个微妙但非常关键的点。使用 LYAH 的原始示例:

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

这与以下内容相同:

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

这里的关键是purebefore (+),它具有拳击(+)作为 Applicative 的效果。如果您查看如何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

所以总而言之,xafterf不是我们二元运算符的第一个参数,它用于 UNBOX 内部的运算符pure

于 2015-06-14T08:16:29.497 回答
19

“在这种情况下,((->)r)代替fr->(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]
于 2012-08-04T18:23:40.443 回答
1

如何理解Function as FunctorFunction as Applicative

首先,如何理解函数作为函子?

我们可以将函子视为一个空盒子,例如:

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

在那里,Maybetype 可以看作是一个带有一个插槽的空框,它采用 type 来生成具体的 type Maybe a。在fmap函数中:

  • 第一个参数是一个函数,从a映射到b;
  • 第二个参数是槽填充的类型的值(具体类型),这个具体类型由类型构造函数生成,类型为faf is Maybe,所以fa is Maybe a)。

当我们实现函数函子时,因为函子函子必须有两个参数才能构成一个类型a -> b,如果我们想让函数函子只有一个槽,我们应该先填一个槽,所以函数函子的类型构造函数是 ((->) r ):

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

fmap和 Functor 中的函数一样Maybe,我们应该把第二个参数g看作是由f ( f等于)生成的一个具体类型的值(->) r,所以fa可以(->) r a看作是r -> a。最后,不难理解,函数中的gx不能fmap看成是,它只是一个函数应用,也r -> x可以看成是。(r -> a) x(x -> a)

最后,不难理解Applicative函数中的<*>函数(->) 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)

因为grr映射到afrar, a映射到b,所以整个 lambda 函数也可以看作r -> b, f b。例如:

((+) <*> (+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 

我们可以简单地将这种应用风格视为 first 计算 after 的值<$>并与 operator before 相加<$>。在上一个例子中,这个运算符是一个二元运算符 equals (+),我们可以将它替换为一个三元运算符(\x y z -> [x,y,z]),所以下面的等式成立:

(\x y z -> [x,y,z]) <$> (+3) <*> (*2) <*> (/2) $ 5 = [8.0,10.0,2.5]
于 2020-02-27T08:31:20.983 回答