Generic solution to (Eq, Show) overlapping instances issue when defining class (* -> *)
Stack 在重叠实例上有很多线程,虽然这些有助于解释问题的根源,但我仍然不清楚如何重新设计我的代码以使问题消失。虽然我肯定会投入更多的时间和精力来研究现有答案的细节,但我将在这里发布我确定为造成问题的一般模式,希望存在一个简单而通用的答案:我通常会发现自己定义一个类,例如:
1 2 3 4
| {-# LANGUAGE FlexibleInstances #-}
class M m where
foo :: m v -> Int
bar :: m v -> String |
连同实例声明:
1 2 3 4 5
| instance (M m ) => Eq (m v ) where
(==) x y = (foo x ) == (foo y ) -- details unimportant
instance (M m ) => Show (m v ) where
show = bar -- details unimportant |
在我的工作过程中,我不可避免地会创建一些数据类型:
并将 A 声明为类 M:
的实例
1 2 3
| instance M A where
foo x = 1 -- details unimportant
bar x ="bar" |
然后定义A Integer的一些元素:
打印 x 和 y 或评估布尔 x == y 没有问题,但如果我尝试打印列表 [x] 或评估布尔 [x] == [y],则会发生重叠实例错误:
1 2 3 4 5 6
| main = do
print x -- fine
print y -- fine
print (x == y ) -- fine
print [x ] -- overlapping instance error
if [x ] == [y ] then return () else return () -- overlapping instance error |
我认为这些错误的原因现在非常清楚:它们源于现有的实例声明 instance Show a => Show [a] 和 instance Eq a => Eq [a],虽然确实 [] :: * -> * 还没有被声明为我的类的实例 M,没有什么可以阻止某人在某些时候这样做:因此编译器会忽略实例声明的上下文。
当面对我所描述的模式时,如何重新设计它来避免问题?
在实例搜索中没有回溯。实例完全基于实例头部的句法结构进行匹配。这意味着在实例解析期间不考虑实例上下文。
所以,当你写
1 2
| instance (M m ) => Show (m v ) where
show = bar |
您是在说"这是 Show 的一个实例,适用于任何类型的 m v"。由于 [x] :: [] (A Int) 确实是 m v 形式的类型(设置 m ~ [] 和 v ~ A Int),因此对 Show [A Int] 的实例搜索会出现两个候选:
就像我说的那样,类型检查器在选择实例时不会查看实例的上下文,因此这两个实例是重叠的。
解决方法是不声明像 Show (m v) 这样的实例。作为一般规则,声明其头部纯粹由类型变量组成的实例是一个坏主意。您编写的每个实例都应该从一个诚实的类型构造函数开始,并且您应该怀疑不符合该模式的实例。
为您的默认实例提供 newtype 是一种相当标准的设计(例如,参见 WrappedBifunctor\\ 的 Functor 实例),
1 2 3 4
| newtype WrappedM m a = WrappedM { unwrapM :: m a }
instance M m => Show (WrappedM m a ) where
show = bar . unwrapM |
as 在顶层给出函数的默认实现(参见例如 foldMapDefault):