如何在Haskell中理解":t((==)<*>)"?

ked*_*bug 28 haskell types type-inference ghc

我是Haskell的新手,遇到麻烦<*>:

((==) <*>) :: Eq a => (a -> a) -> a -> Bool
Run Code Online (Sandbox Code Playgroud)

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

Gab*_*lez 57

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

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

(1 +) :: Int -> Int
Run Code Online (Sandbox Code Playgroud)

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

\x -> 1 + x
Run Code Online (Sandbox Code Playgroud)

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

((==) <*>)

= \g -> (==) <*> g
Run Code Online (Sandbox Code Playgroud)

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

class (Functor f) => Applicative f where
    pure  :: a -> f a
    (<*>) :: f (a -> b) -> f a -> f b
Run Code Online (Sandbox Code Playgroud)

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

instance Applicative ((->) r) where
    pure  :: a -> ((->) r) a
    (<*>) :: (->) r (a -> b) -> (->) r a -> (->) r b
Run Code Online (Sandbox Code Playgroud)

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

instance Applicative ((->) r) where
    pure  :: a -> r -> a
    (<*>) :: (r -> a -> b) -> (r -> a) -> (r -> b)
Run Code Online (Sandbox Code Playgroud)

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

(==) :: Eq e => e -> e -> Bool
Run Code Online (Sandbox Code Playgroud)

因此,如果我们把它传递给(<*>)编译器可以推断出更多的类型r,a以及b在签名(<*>):

 (<*>) ::        (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`
Run Code Online (Sandbox Code Playgroud)

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

((==) <*>) :: Eq e => (e -> e) -> (e -> Bool)
Run Code Online (Sandbox Code Playgroud)

可以去掉右边括号,因为(->)是右结合,并改变ea让你得到了最终的签名:

((==) <*>) :: Eq a => (a -> a) -> a -> Bool
Run Code Online (Sandbox Code Playgroud)

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

(f <*> g) r = f r (g r)
Run Code Online (Sandbox Code Playgroud)

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

((==) <*> g) r = (==) r (g r)
Run Code Online (Sandbox Code Playgroud)

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

((==) <*> g) r = r == (g r)
Run Code Online (Sandbox Code Playgroud)

这相当于:

((==) <*>) = \g r -> r == g r
Run Code Online (Sandbox Code Playgroud)

这意味着你的函数需要两个参数:gr,然后看看是否r等于g r.

  • @ user2407038不太好.考虑函数`gr = r*10`.`g 0 == 0`,但这并不意味着`g`是身份函数!`((==)<*>)`接受函数*和*输入,并测试函数是否将*特定输入*映射到自身. (12认同)
  • @ shree.pat18不是因为它不会得到/给你一个固定的点.它只告诉你你提供的值`r`是否是'g`的固定点. (5认同)
  • @Ben 不。它只需要一个特定的“r”,对其应用一次“g”,然后查看它是否与原始的“r”匹配。它没有针对所有“r”进行测试。相反,它仅检查一个特定的“r”和函数“g”的一个应用程序。 (2认同)
  • @GabrielGonzalez"免责声明:这不是惯用的Haskell代码"是什么意思? (2认同)
  • @erisco编写这样的代码不是一个好习惯。不必要地造成混乱 (2认同)