(<*>) :: f (a -> b) -> f a -> f b 在 Functor class 中究竟做了什么

what does (<*>) :: f (a -> b) -> f a -> f b exactly do in the Functor class

class Functor f => Applicative f where
       pure :: a -> f a
       (<*>) :: f (a -> b) -> f a -> f b

根据我的理解,它需要一个函数 f,其中另一个函数 (a -> b) 作为它的参数,returns 一个函数 f。将 f 应用到 a 然后 returns 一个函数 f 并将 f 应用到 b.

这是一个例子:

Prelude> (+) <$> Just 2 <*> Just 3
Just 5

但是我不太明白它是如何工作的。

我猜(+)应该是fJust 2Just 3应该分别是ab。那什么是(a -> b)呢?

From my understanding, it takes a function f...

不幸的是,这是不正确的。在这种情况下,f 是一个类型,而不是一个函数。具体来说,f 是种类 * -> * 的 "higher-kinded type"。类型 f 是函子。

在这种情况下,fMaybe。所以我们可以重写函数类型,将它们专门化为 Maybe:

pure :: a -> Maybe a
(<*>) :: Maybe (a -> b) -> Maybe a -> Maybe b

一旦你走到这一步,它就会开始变得有点清晰。 pure 有几个不同的可能定义,但只有一个有意义:

pure = Just

运算符x <$> ypure x <*> y是一样的,所以如果写成:

(+) <$> Just 2 <*> Just 3

那么我们可以改写为:

pure (+) <*> pure 2 <*> pure 3

虽然这在技术上有更通用的类型。使用函子定律,我们知道 pure x <*> pure ypure (x y) 相同,所以我们得到

pure ((+) 2) <*> pure 3
pure ((+) 2 3)
pure (2 + 3)

在这种情况下,我们 ab 类型 但是由于 <*> 出现了两次,它们实际上在每个中都有不同的类型案例.

在第一个<*>中,aIntbInt -> Int。 在第二个<*>中,ab都是Int。 (从技术上讲,您可以获得 Int 的通用版本,但这对问题来说并不重要。)

应用仿函数被引入 Haskell 作为应用风格编程 "Idioms"。解包这个短语,我们有 "applicative style programming";这只是将函数应用于参数。我们还有 "idioms" 或具有特殊含义的语言短语。例如 "raining cats and dogs" 是表示下大雨的成语。把它们放在一起,applicative functor就是具有特殊意义的函数应用。

举个例子,在 Dietrich Epp 的带领下,anApplication 由函数定义,

anApplication = f a
  where
    f = (+2)
    a = 3

并且,anIdiomaticApplication,用惯用的应用程序定义,

anIdiomaticApplication = f <*> a
   where 
     f = Just (+2)
     a = Just 3

这些定义的顶层结构相似。区别?第一个有 space——正常的函数应用——第二个有 <*>——惯用的函数应用。这说明了 <*> 如何促进应用风格:只需使用 <*> 代替 space.

应用程序 <*> 是惯用的,因为它具有不仅仅是纯功能应用程序的含义。作为说明,在 anIdiomaticApplication 中我们有这样的东西:

 f <*> a :: Maybe (Int -> Int) <*> Maybe Int

这里,type中的<*>用来表示一个类型级别的函数*,对应真实<*>的签名。对于 type-<*>,我们应用 fa 的类型参数(分别为 Maybe (Int -> Int)Maybe Int)。申请后我们有

 f <*> a :: Maybe Int

作为中间步骤,我们可以想象这样的事情

 f <*> a :: Maybe ((Int -> Int) _ Int)

_ 是常规函数应用程序的类型级别替代。

到这里终于可以看到成语的叫法了。 f <*> a 就像一个普通的函数应用程序,(Int -> Int) _ Int,在Maybe context/idiom 中。因此,<*> 只是在特定上下文中发生的函数应用。

临别,我要强调的是理解<*>只是部分理解它的使用。我们可以理解为 f <*> a 只是一些额外的惯用意义的函数应用。由于应用法则,我们还可以假设惯用应用在某种程度上是明智的。

但是,如果您看到 <*> 并因为内容太少而感到困惑,请不要感到惊讶。我们还必须精通各种 Haskell 成语。例如,在 Maybe 习语中,函数或值可能不存在,在这种情况下输出将是 Nothing。当然还有很多其他的,但是只要熟悉 Either aState s 就可以模拟各种不同的类型。

*像这样的东西实际上可以用一个封闭的类型系列来制作(未经测试)

type family IdmApp f a where
   IdmApp (f (a->b)) a = f b