27

我是 Haskell 的新手,在这里遇到麻烦<*>

((==) <*>) :: Eq a => (a -> a) -> a -> Bool

我如何理解这一点以及如何推断?

4

1 回答 1

57

免责声明:这不是惯用的 Haskell 代码。

优先考虑的第一件事是<*>. 当您看到一个运算符仅应用于一个称为部分的参数时。下面是一个更常见的运算符部分的示例:

(1 +) :: Int -> Int

这是一个部分适用+于 1 的函数,为最后一个参数留出了空间。它相当于:

\x -> 1 + x

因此,在您的代码示例中,<*>部分应用于(==),因此我们将其扩展为:

((==) <*>)

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

接下来,您需要了解<*>它在做什么。这是Applicative类型类的成员:

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

这意味着它<*>被重载以适用于任何实现Applicative. Applicative该类型的一个实例是((->) r)

instance Applicative ((->) r) where
    pure  :: a -> ((->) r) a
    (<*>) :: (->) r (a -> b) -> (->) r a -> (->) r b

(->)以前缀形式使用的均值周围的括号(在定义像这样的类实例时,出于语法原因,这是必要的)。如果将其扩展为中缀形式,您将得到:

instance Applicative ((->) r) where
    pure  :: a -> r -> a
    (<*>) :: (r -> a -> b) -> (r -> a) -> (r -> b)

在您的具体示例中,第一个参数<*>(==)运算符,它具有以下类型:

(==) :: Eq e => e -> e -> Bool

因此,如果我们将其传递给(<*>)编译器,则可以推断出更多关于 、 和 的类型的信息rab在 for 的签名中(<*>)

 (<*>) ::        (r -> a -> b   ) -> (r -> a) -> (r -> b)
 (==)  :: Eq e => e -> e -> Bool
                  |    |    |
                  |    |    +-> `b` must be `Bool`
                  |    |
                  |    +------> `a` must be `e`
                  |
                  +-----------> `r` must also be `e`

因此,当我们提供(==)作为第一个参数时,(<*>)我们会得到这个推断类型:

((==) <*>) :: Eq e => (e -> e) -> (e -> Bool)

您可以省略右括号,因为(->)它是右结合的,并更改ea给出您得到的最终签名:

((==) <*>) :: Eq a => (a -> a) -> a -> Bool

但这实际上在做什么?要了解我们需要了解如何(<*>)为 的Applicative实例定义((->) r)

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

如果我们替换f(==)我们得到:

((==) <*> g) r = (==) r (g r)

当我们(==)用括号括起来时,这意味着我们在前缀符号中使用它。这意味着如果我们删除括号并将其改回中缀符号,我们会得到:

((==) <*> g) r = r == (g r)

这相当于:

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

这意味着您的函数需要两个参数:gand r,然后查看是否r等于g r

于 2013-09-15T15:10:37.713 回答