(r ->) 应用函子

(r ->) applicative functor

我无法理解 Applicative 的函数实例 (->) r 在 Haskell 中的工作原理。

例如,如果我有

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

我知道你得到了结果 508,我知道你得到了 (5 + 3)(5 * 100) 的结果,然后将 (+) 函数应用于这两个。

不过我不是很明白这是怎么回事。我假设表达式用括号括起来如下:

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

根据我的理解,正在发生的事情是您将 (+) 映射到 (+3) 的最终结果,然后您正在使用 <*> 运算符将该函数应用于最终结果(*100)

但是我不明白 (->) r 实例的 <*> 的实现以及为什么我不能写:

(+3) <*> (*100)

<*><$> 运算符在涉及 (->) r 时如何工作?

<$> 只是 fmap 的另一个名称,它对 (->) r 的定义是 (.)(组合运算符):

intance Functor ((->) r) where
  fmap f g = f . g

你基本上可以通过查看类型来计算 <*> 的实现:

instance Applicative ((->) r) where
  (<*>) :: (r -> a -> b) -> (r -> a) -> (r -> b)
  f <*> g = \x -> f x (g x)

您有一个从 rab 的函数和一个从 ra 的函数。结果你想要一个从 rb 的函数。你知道的第一件事是你 return 一个函数:

\x ->

现在您要应用 f,因为它是唯一可以 return 成为 b:

的项目
\x -> f _ _

现在 f 的参数是 ra 类型。 r 只是 x(因为它已经是 r 类型,您可以通过将 g 应用于 x 来获得 a

\x -> f x (g x)

啊啊,大功告成。 Here's a link to the implementation in Haskell's Prelude.

让我们来看看这些函数的类型(以及我们自动与它们相处的定义):

(<$>) :: (a -> b) -> (r -> a) -> r -> b
f <$> g = \x -> f (g x)

(<*>) :: (r -> a -> b) -> (r -> a) -> r -> b
f <*> g = \x -> f x (g x)

在第一种情况下,<$>,实际上只是函数组合。一个更简单的定义是 (<$>) = (.).

第二种情况有点混乱。我们的第一个输入是一个函数f :: r -> a -> b,我们需要得到一个b类型的输出。我们可以提供 x :: r 作为 f 的第一个参数,但是我们可以将 a 作为第二个参数的类型的唯一方法是将 g :: r -> a 应用于 [=15] =].


有趣的是,<*> 实际上是 SKI combinatory calculusS 函数,而 (-> r)pureK :: a -> b -> a (常数)函数。

考虑 <*> 的类型签名:

(<*>) :: Applicative f => f (a -> b) -> f a -> f b

将其与普通函数应用程序的类型签名进行比较,$:

($) :: (a -> b) -> a -> b

请注意,它们非常相似!事实上,<*> 运算符有效地泛化了应用程序,因此它可以根据所涉及的类型 重载 。这个用最简单的Applicative,Identity:

就很容易看出来
ghci> Identity (+) <*> Identity 1 <*> Identity 2
Identity 3

这也可以用稍微复杂一点的应用函子看到,比如Maybe:

ghci> Just (+) <*> Just 1 <*> Just 2
Just 3
ghci> Just (+) <*> Nothing <*> Just 2
Nothing

对于 (->) rApplicative 实例执行一种函数组合,它生成一个新函数,该函数接受一种“上下文”并将其线程化到所有值以生成函数及其参数:

ghci> ((\_ -> (+)) <*> (+ 3) <*> (* 100)) 5
508

在上面的例子中,我只使用了<*>,所以我明确写出第一个参数忽略它的参数并且总是产生(+)。但是,Applicative 类型类还包括 pure 函数,它具有将纯值“提升”为应用函子的相同目的:

ghci> (pure (+) <*> (+ 3) <*> (* 100)) 5
508

但在实践中,您很少会看到 pure x <*> y,因为根据 Applicative 法则它恰好等同于 x <$> y,因为 <$> 只是一个中缀同义词fmap。因此,我们有了常用的成语:

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

更一般地说,如果您看到任何类似这样的表达式:

f <$> a <*> b

...你可以或多或少地像普通函数应用程序 f a b 一样阅读它,除了在特定 Applicative 实例的习语的上下文中。事实上,Applicative 的原始表述提出了“成语括号”的想法,即为上述表达式添加以下语法糖:

(| f a b |)

然而,Haskellers 似乎对中缀运算符非常满意,认为添加额外语法的好处不值得付出代价,因此 <$><*> 仍然是必要的。

作为我自己的 Haskell 新手,我会尽我所能解释最好的方法

<$> 运算符与将一个函数映射到另一个函数相同。

当你这样做时:

(+) <$> (+3)

你基本上是这样做的:

fmap (+) (+3)

以上将调用 (->) r 的 Functor 实现,如下所示:

fmap f g = (\x -> f (g x))

所以fmap (+) (+3)的结果是(\x -> (+) (x + 3))

请注意,此表达式的结果类型为 a -> (a -> a)

这是一个应用程序!这就是为什么您可以将 (+) <$> (+3) 的结果传递给 <*> 运算符!

您可能会问为什么它是一个应用程序?让我们看看 <*> 定义:

f (a -> b) -> f a -> f b 

注意第一个参数匹配我们返回的函数定义a -> (a -> a)

现在,如果我们看一下 <*> 运算符的实现,它看起来像这样:

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

所以当我们将所有这些部分放在一起时,我们得到:

(+) <$> (+3) <*> (+5)
(\x -> (+) (x + 3)) <*> (+5)
(\y -> (\x -> (+) (x + 3)) y (y + 5))
(\y -> (+) (y + 3) (y + 5))

(->) e FunctorApplicative 实例往往有点令人困惑。将 (->) e 视为 Reader e 的 "undressed" 版本可能会有所帮助。

newtype Reader e a = Reader
  { runReader :: e -> a }

名字 e 应该暗示单词 "environment"。类型 Reader e a 应读作 "a computation that produces a value of type a given an environment of type e".

给定类型 Reader e a 的计算,您可以修改其输出:

instance Functor (Reader e) where
  fmap f r = Reader $ \e -> f (runReader r e)

即先运行给定环境下的计算,再应用映射函数

instance Applicative (Reader e) where
  -- Produce a value without using the environment
  pure a = Reader $ \ _e -> a

  -- Produce a function and a value using the same environment;
  -- apply the function to the value

  rf <*> rx = Reader $ \e -> (runReader rf e) (runReader rx e)

您可以像任何其他应用函子一样使用通常的 Applicative 推理。