在Haskell中,为什么需要在实例声明中指定typeclass?

在Haskell中,为什么需要在实例声明中指定typeclass?,haskell,Haskell,以下内容无法编译: data Point a b = Point { x :: a , y :: b } deriving (Show) instance Eq (Point a b) where (Point x _) == (Point y _) = x == y 错误是: No instance for (Eq a) arising from a use of `==' In the e

以下内容无法编译:

data Point a b = Point { x :: a
                       , y :: b
                       } deriving (Show)

instance Eq (Point a b) where
  (Point x _) == (Point y _) = x == y
错误是:

No instance for (Eq a)
  arising from a use of `=='
In the expression: x == y
In an equation for `==': (Point x _) == (Point y _) = x == y
In the instance declaration for `Eq (Point a b)'
但是,如果我将typeclass添加到实例中,那么它可以工作:

data Point a b = Point { x :: a
                       , y :: b
                       } deriving (Show)

instance (Eq a) => Eq (Point a b) where
  (Point x _) == (Point y _) = x == y
编译器难道看不出我在那里使用了
a==a
,并推断
a
必须在typeclass
Eq
中吗?

可以推断
a
必须在type class
Eq
中。这正是它抱怨的原因。您声明了
实例Eq(点ab)
,这表示对于任何类型
a
b
,表单
点ab
的类型都在
Eq
类型类中,但您给出了
=
的定义,该定义仅在
a
Eq
的成员时有效

这两件事是不一致的,所以Haskell不会试图猜测哪一件是你真正的意思,它只是将其报告为一个错误。该语言不必以这种方式工作,但它是经过深思熟虑的设计选择。

想象一个函数

equals ::  a -> a -> Bool
equals = (==)
编译器可以很明显地推断出equals的类型应该是
eqa=>a->a->Bool
。但它会出错,因为您声明您的类型适用于所有a

Typeclass实例是类似的,只是我们没有选择让它们以同样的方式被推断,我们可以省略
equals
的类型声明。因此,有必要指定约束,就像指定函数的类型签名一样,还必须指定约束

也就是说,ghc不会仅推断约束;它要么推断出整个签名,要么一个也不推断。(好吧,不管是哪种方式,它都可以推断,但是如果你输入了它,那么推断必须比你输入的更一般——而约束也属于这一要求)


把你最初的
实例Eq(点ab)
想象成
(==)::(点ab)->(点ab)->Bool
,这是不可能以你喜欢的方式很好地定义的,因为给它主体将有
(==)::Eq a=>(点ab)->(点ab)->Bool

我有没有办法不用写
Eq a
就写上面的代码?@Eyal没有,除非你希望你的Eq更标准(即使用y记录),在这种情况下你可以Eq@Eyal:不。你为什么要这样做?@Eyal:如果允许的话,这将使确定给定实例的类型类方法的参数所需的约束变得更加困难。如果需要在实例头中放置约束,则只需查看实例定义的第一行。如果您不是必需的,那么您在实例头中的实际类型上几乎是在撒谎,因为类似于
点ab
(没有约束)这意味着这适用于Haskell中的所有类型
a
b
。@备选方案:点a b的Eq的派生实例将对
a
b
都有Eq约束,因此您并没有真正避免它。您的
Eq
实例声明在我看来很可疑。除了你的代码对编译器所说的之外,它还告诉世界,一个点的第二个组成部分并不重要,也许只是出于某种内部优化的目的。另一方面,你的
Show
实例则相反,这两个组件都很重要。如果第二个组件真的不重要,那么你不应该派生
Show
,而是应该做一些模糊的事情,比如
instance(Show a,Show b)=>Show(a点b)where showsPrec_x(y点)s=“点”++show x
然后,为了进行调试,编写您自己的函数
showdirty洗衣房(点AB)=“点”++show a++show b
。但是,如果第二个组件对世界其他地方很重要,那么您应该使用
派生(Eq,Show)
,让一切变得更清晰。在某些情况下,使用
派生
也可能有效率方面的好处,因此在适当的时候使用它。您编写的typeclass实例没有让它们推断为do函数的选项。为什么不呢?在lambda微积分或代数型系统中有什么东西使它不可能实现吗?还是哈斯凯尔的设计师们干脆把它漏掉了?