作为applicative functors(Haskell / LYAH)

时间:2012-08-04 18:07:34

标签: haskell applicative

了解您的Haskell 第11章介绍了以下定义:

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

在这里,作者参与了一些不寻常的挥手(“&lt; *&gt;的实例实现有点神秘,所以如果我们只是[在没有解释的情况下展示它的话]它是最好的”)。我希望这里的某个人可以帮我解决这个问题。

根据应用类定义,(<*>) :: 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 个答案:

答案 0 :(得分:33)

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

fmap f x = pure f <*> x

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

现在,让我们考虑为什么实例的编写方式。 <*>所做的基本上是将函数中的函数应用于函子中的值。专门用于(->) r,这意味着它将函数返回的函数r应用于r函数返回的值。返回函数的函数只是两个参数的函数。所以真正的问题是:如何将两个参数的函数(ra,返回b)应用于a函数返回的值r {1}}?

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

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

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

所以最后的想法很简单:我们使用参数首先通过将a插入g来获取r类型的值。参数的类型为gr -> a的类型为a,因此我们有一个f。然后,我们将参数和新值都插入f。我们需要两者,因为r -> a -> b的类型为r。在我们同时插入ab1之后,我们就会有r -> b。由于参数是lambda,结果的类型为{{1}},这就是我们想要的。

答案 1 :(得分:20)

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

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

这与:

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

此处的关键是pure之前的(+),其效果是将(+)作为一个应用来装箱。如果你看看如何定义pure,你可以看到要取消它,你需要提供一个额外的参数,可以是任何东西。将<*>应用于(+) <$> (+3),即可获得

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

请注意(pure (+)) x,我们将x应用于pure至unbox (+)。所以我们现在有了

\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

总而言之,x之后的f不是我们的二元运算符的第一个参数,它用于UNBOX pure内的运算符。

答案 2 :(得分:14)

  

“在实例中,用((->)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]

答案 3 :(得分:0)

如何理解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

那里的Maybe类型可以看作是一个带有一个插槽的空盒子,该类型采用一种类型来生成具体的类型Maybe a。在fmap函数中:

  • 第一个参数是一个函数,它从a映射到b;
  • 第二个参数是填充了插槽的类型的值(具体类型),此具体类型由类型构造函数生成,类型为 fa f < / em>是Maybe,所以 fa Maybe a)。

当我们实现函数函子时,因为函数函子必须具有两个参数才能形成类型a -> b,如果我们希望函数函子只有一个插槽,则应首先填充一个插槽,因此函数的类型构造函数函子是((->)r):

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

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

最后,不难理解应用功能(x -> a)中的<*>功能可以实现如下:

(->) r
gr

r 映射到 a fra r,a映射 b ,因此整个lambda函数可以看作<*> :: 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) ,也可以看作r -> b。例如:

f b

结果是5 +(5 + 3)= 13。

如何在应用函数中理解((+) <*> (+3)) 5 = 508?

我们知道(+) <$> (+3) <*> (*100) $ 5的类型为:(+);

我们也知道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)

然后我们将此函数与参数5一起应用,我们得到:

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

我们可以简单地认为这种应用样式是首先计算(5 + 3) + (5 * 100) = 508 之后的值,并将它们与<$>之前的运算符求和。在最后一个示例中,该运算符是等于<$>的二进制运算符,我们可以用三元运算符(+)代替它,因此以下等式成立:

(\x y z -> [x,y,z])