Stack在重叠实例上有很多线程,虽然这些有助于解释问题的根源,但我仍然不清楚如何重新设计我的代码以解决问题。虽然我肯定会花更多的时间和精力来完成现有答案的细节,但我会在这里发布我已经确定为创建问题的一般模式,希望存在一个简单而通用的答案:我通常会发现自己定义一个类如:
{-# LANGUAGE FlexibleInstances #-}
class M m where
foo :: m v -> Int
bar :: m v -> String
与实例声明一起:
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
在我的工作过程中,我将不可避免地创建一些数据类型:
data A v = A v
并将A
声明为类M
的实例:
instance M A where
foo x = 1 -- details unimportant
bar x = "bar"
然后定义A Integer
的一些元素:
x = A 2
y = A 3
打印x
和y
或评估布尔x == y
时没有问题,但是如果我尝试打印列表[x]
或评估布尔[x] == [y]
},然后发生重叠实例错误:
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
的一个实例,没有什么能阻止某人在某个时刻这样做:所以编译器会忽略实例声明的上下文。
当面对我所描述的模式时,如何重新设计以避免这个问题呢?
答案 0 :(得分:1)
实例搜索中没有回溯。实例纯粹基于实例头的语法结构进行匹配。这意味着在实例解析期间不会考虑实例上下文。
所以,当你写
instance (M m) => Show (m v) where
show = bar
您正在说"以下是Show
的实例,适用于任何类型的m v
"。由于[x] :: [] (A Int)
确实是m v
(设置m ~ []
和v ~ A Int
)的一种形式,因此Show [A Int]
的实例搜索会出现两个候选人:
instance Show a => Show [a]
instance M m => Show (m v)
就像我说的那样,类型检查器并没有查看实例'选择实例时的上下文,因此这两个实例重叠。
修复方法是不声明像Show (m v)
这样的实例。作为一般规则,声明其头部纯粹由类型变量组成的实例是一个坏主意。你编写的每一个实例都应该以诚实为善的类型构造函数开始,你应该接触那些不适合这种模式的实例。
为默认实例提供newtype
是一种相当标准的设计(例如,参见WrappedBifunctor
&#39} Functor
个实例),
newtype WrappedM m a = WrappedM { unwrapM :: m a }
instance M m => Show (WrappedM m a) where
show = bar . unwrapM
在顶层提供函数的默认实现(参见例如foldMapDefault
):
showDefault = bar