定义类时(* - > *)定义(Eq,Show)重叠实例的一般解决方案 [英] Generic solution to (Eq, Show) overlapping instances issue when defining class (* -> *)

查看:145
本文介绍了定义类时(* - > *)定义(Eq,Show)重叠实例的一般解决方案的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

Stack在重叠实例上有很多线程,虽然这些有助于解释问题的根源,但我仍然不清楚如何重新设计我的代码以使问题消失。尽管我肯定会投入更多的时间和精力去研究现有答案的细节,但我会在这里发布我认为会产生问题的一般模式,希望能够找到一个简单而通用的答案:我通常会发现自己定义一个类,例如:

  { - #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

然后定义一个整数
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)。作为一般规则,声明头部纯粹由类型变量组成的实例是一个坏主意。你写的每一个实例都应该从一个诚实善良的类型构造函数开始,并且你应该接近那些怀疑不符合该模式的实例。



提供一个<$对于默认实例,c $ c> newtype 是一个相当标准的设计(参见,例如 WrappedBifunctor Functor 实例),

  newtype WrappedM ma = WrappedM {unwrapM :: ma} 

实例M m => Show(WrappedM m a)其中
show = bar。 unwrapM

,因为在顶层提供了函数的默认实现(请参阅 foldMapDefault

  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

这篇关于定义类时(* - &gt; *)定义(Eq,Show)重叠实例的一般解决方案的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆