定义类时(* - > *)定义(Eq,Show)重叠实例的一般解决方案 [英] Generic solution to (Eq, Show) overlapping instances issue when defining class (* -> *)
问题描述
{ - #LANGUAGE FlexibleInstances# - }
class M m其中
foo :: mv - > Int
bar :: m v - >字符串
连同实例声明:
instance(M m)=>方程(m v)其中
(==)x y =(foo x)==(foo y) - 细节不重要
实例(M m)=>显示(mv)其中
show = bar - 详细信息不重要
我的工作我将不可避免地创建一些数据类型:
data A v = A v
并声明 A
作为类 M $的一个实例
$ pre $ code $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ =bar
然后定义 我没有问题打印 这些错误的原因现在非常清晰我认为:它们源于现有的实例声明 当面对我描述的模式时,如何重新设计以避免问题? 实例搜索中没有回溯。实例只是基于实例头的语法结构进行匹配。这意味着实例上下文在实例解析过程中没有被考虑到。 所以,当你写下 你在说这是一个实例对于 正如我所说的,类型检查器在查看实例的上下文时不会查看选择一个实例,所以这两个实例是重叠的。 修正是不声明像 提供一个<$对于默认实例,c $ c> newtype 一个整数$ c
是一个相当标准的设计(参见,例如
pre code $ x $ A $ $ $ $ $ = x
和 y
或评估布尔 x == y
,但是如果我尝试打印列表 [x]
或评估布尔值 [x] == [y]
,则发生重叠实例错误:
main = do
print x - fine
print y - 罚款
print(x == y) - 罚款
print [x] - 重叠实例错误
if [x] == [y] then return()else return( ) - 重叠的实例错误
instance Show a =>显示[a]
和实例Eq a =>公式[a]
,虽然确实如此, [] :: * - > *
还没有被声明为我的类的一个实例 M
,没有什么能够阻止某人在某个时候这样做:所以编译器会忽略实例声明的上下文。
instance(M m)=> Show(mv)where
show = bar
显示
,对于任何形式的 mv
。由于 [x] :: [](A Int)
的确是一种形式 mv
(set m〜[]
和 v〜A Int
),实例搜索 Show [A Int]
出现了两位候选人:
instance显示a =>显示[a]
实例M m =>显示(mv)
Show(mv)
。作为一般规则,声明头部纯粹由类型变量组成的实例是一个坏主意。你写的每一个实例都应该从一个诚实善良的类型构造函数开始,并且你应该接近那些怀疑不符合该模式的实例。
WrappedBifunctor
的 Functor
实例),
newtype WrappedM ma = WrappedM {unwrapM :: ma}
实例M m => Show(WrappedM m a)其中
show = bar。 unwrapM
,因为在顶层提供了函数的默认实现(请参阅 foldMapDefault $ c $
showDefault = bar
Stack has many threads on overlapping instances, and while these are helpful in explaining the source of the problem, I am still not clear as to how to redesign my code for the problem to go away. While I will certain invest more time and effort in going through the details of existing answers, I will post here the general pattern which I have identified as creating the problem, in the hope that a simple and generic answer exists: I typically find myself defining a class such as:
{-# LANGUAGE FlexibleInstances #-}
class M m where
foo :: m v -> Int
bar :: m v -> String
together with the instance declarations:
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
and in the course of my work I will inevitably create some data type:
data A v = A v
and declare A
as an instance of class M
:
instance M A where
foo x = 1 -- details unimportant
bar x = "bar"
Then defining some elements of A Integer
:
x = A 2
y = A 3
I have no issue printing x
and y
or evaluating the Boolean x == y
, but if I attempt to print the list [x]
or evaluate the Boolean [x] == [y]
, then the overlapping instance error occurs:
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
The cause of these errors is now very clear I think: they stem from the existing instance declarations instance Show a => Show [a]
and instance Eq a => Eq [a]
and while it is true that [] :: * -> *
has not yet been declared as an instance of my class M
, there is nothing preventing someone doing so at some point: so the compiler ignores the context of instance declarations.
When faced with the pattern I have described, how can it be re-engineered to avoid the problem?
There's no backtracking in instance search. Instances are matched purely based on the syntactic structure of the instance head. That means instance contexts are not accounted for during instance resolution.
So, when you write
instance (M m) => Show (m v) where
show = bar
you're saying "Here is an instance for Show
, for any type of the form m v
". Since [x] :: [] (A Int)
is indeed a type of the form m v
(set m ~ []
and v ~ A Int
), instance search for Show [A Int]
turns up two candidates:
instance Show a => Show [a]
instance M m => Show (m v)
Like I said, the type checker doesn't look at the instances' contexts when selecting an instance, so these two instances are overlapping.
The fix is to not declare instances like Show (m v)
. As a general rule, it's a bad idea to declare instances whose head is composed purely of type variables. Every instance you write should start with an honest-to-goodness type constructor, and you should approach instances which don't fit that pattern with suspicion.
Supplying a newtype
for your default instances is a fairly standard design (see, for example, WrappedBifunctor
's Functor
instance),
newtype WrappedM m a = WrappedM { unwrapM :: m a }
instance M m => Show (WrappedM m a) where
show = bar . unwrapM
as is giving a default implementation of the function at the top level (see eg foldMapDefault
):
showDefault = bar
这篇关于定义类时(* - > *)定义(Eq,Show)重叠实例的一般解决方案的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!