为什么使用模式匹配构造的函数具有 Eq 类型约束,但在使用数据构造函数时却没有?

Why does a function constructed with pattern matching have the Eq type constraint but not when using a data constructor?

为什么 ghci 在我通过模式匹配构建的这个函数 matchInt 的类型签名中列出了一个 equality type 约束:

$ ghci
GHCi, version 8.2.1: http://www.haskell.org/ghc/  :? for help
Prelude> :{
Prelude| matchInt 1 = 3
Prelude| matchInt _ = 4
Prelude| :}
Prelude> matchInt 1
3
Prelude> matchInt 22
4
Prelude> :t matchInt
matchInt :: (Eq a, Num a, Num p) => a -> p

相比之下,当使用简单的数据构造函数时,没有相等类型约束。

$ ghci
GHCi, version 8.2.1: http://www.haskell.org/ghc/  :? for help
Prelude> data Z = Y Int
Prelude> :{
Prelude| matchDataInt (Y 1) = 3
Prelude| matchDataInt _ = 4
Prelude| :}
Prelude> matchDataInt (Y 1)
3
Prelude> matchDataInt (Y 22)
4
Prelude> :t matchDataInt
matchDataInt :: Num p => Z -> p

确实可以比较Z的实例:

Prelude> Y 22 == Y 33
<interactive>:11:1: error:
    • No instance for (Eq Z) arising from a use of ‘==’
    • In the expression: Y 22 == Y 33
      In an equation for ‘it’: it = Y 22 == Y 33

那么,为什么 matchInt 函数将等式列为类型约束而不是函数 matchDataInt

这个question是相关的。但是,如果 matchInt 需要相等性测试,那么为什么 matchDataInt 不需要呢?在这里我谈到了我的关键点:难道 both matchIntmatchDataInt 必须针对 1 进行模式匹配操作测试吗?

matchDataInt 函数不需要 Eq 约束,因为它 专门匹配 IntInts 已经有一个 Eq 个实例。

你的matchInt函数不只是将Ints作为参数——它可以采用任何种数字,只要你能比较那个数字是平等的。这就是为什么它的类型为 (Eq a, Num a, Num p) => a -> p。你也可以给它类型 Num p => Int -> p(将 a 特化为 Int),因为 Int 已经有 EqNum 实例。

另一方面,您的 matchDataInt 函数将 Z 作为参数,每个 Z 都包含一个 Int。针对 的模式匹配 Int 会产生 Eq 约束,但 仅在 Int 上。你可以改为

data Z' a = Y' a

matchDataNum :: (Eq a, Num a, Num p) => Z' a -> p
matchDataNum (Y' 1) = 3
matchDataNum _      = 4

在这里,您无法删除 Eq a 约束。


对于本身没有 return 数字的变体函数,这一切可能会稍微清楚一些。如果我们有

data Z    = Y  Int
data Z' a = Y' a

is1 1 = True
is1 _ = False

isY1 (Y 1) = True
isY1 _     = False

isY'1 (Y' 1) = True
isY'1 _      = False

那么我们定义的三个函数的类型是

is1   :: (Eq a, Num a) => a -> Bool
isY1  :: Z -> Bool
isY'1 :: (Eq a, Num a) => Z' a -> Bool

我们还可以定义

is1Int :: Int -> Bool
is1Int 1 = True
is1Int _ = False

语法上matchInt是建立在模式匹配之上的,但这里的模式匹配是一种错觉。 1 不是数据构造函数。数字文字超载。 1 等同于 fromInteger #1,其中 #1Integer 类型的非重载文字(无法在标准 Haskell 中表达)。你不能真正对这些东西进行模式匹配。

因此编译器允许您编写语法上的模式匹配,但这种表示法实际上表示守卫:

matchInt 1 = ... -- what is written
matchInt x | x == fromInteger #1 = ...  -- what it really means

由于matchInt的类型没有明确给出,所以是推断出来的。它是一个函数,所以类型是 a->b 的一些改进。对 fromInteger 的调用产生了约束 Num a,对 == 的调用产生了约束 Eq a,这就是关于 a 的全部信息].

如果 OTOH 我们给函数一个明确的签名,比如说

matchInt :: Int->Int

那么我们就不需要推断类型了,只检查它是否满足约束条件。因为Int同时满足Eq IntNum Int,所以一切都ok

这就是您的第二个示例中发生的情况。您匹配的类型已知为 Int,不是因为显式类型签名,而是因为它是从 ZY Int 替代项推断出来的。这里再次 Int 已经拥有所有需要的实例。