定义类(* - > *)

时间:2017-05-01 09:47:01

标签: haskell

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

打印xy或评估布尔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的一个实例,没有什么能阻止某人在某个时刻这样做:所以编译器会忽略实例声明的上下文。

当面对我所描述的模式时,如何重新设计以避免这个问题呢?

1 个答案:

答案 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