使用"重叠"

时间:2017-12-01 11:48:07

标签: haskell typeclass

我收到错误:

 Duplicate instance declarations:
   instance [overlap ok] EnumTag a => Read a
     -- Defined at /XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/intero/intero2932Xpa-TEMP.hs:110:27
   instance [overlap ok] StrTag a => Read a
     -- Defined at /XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX/intero/intero2932Xpa-TEMP.hs:121:27 (intero)

对于此代码:

class (Show a, Enum a) => EnumTag a where
  anyEnum :: a

instance {-# OVERLAPS #-} EnumTag a => Read a where
  readPrec = RP.lift P.skipSpaces >> expectEnum
instance {-# OVERLAPS #-} EnumTag a => Eq a where
  a == b | a == anyEnum || b == anyEnum = True
         | otherwise = fromEnum a == fromEnum b

class StrTag a where
  anyStr :: a
  tagPrefix :: a -> String -- ^ should be constant
  toStr :: String -> a

instance {-# OVERLAPS #-} StrTag a => Read a where
  readPrec = parens $ do
    RP.lift P.skipSpaces
    (RP.lift $ expectShown anyStr) <++ RP.lift g
    where g = do
            Just s@(_:_) <- L.stripPrefix tagPrefix <$> expectTag
            return $ toStr s

为什么会这样?第一个实例中的Read a仅在aEnumTag时有效,在第二个实例中 - 仅在aStrTag时才有效。

如何修复此错误并创建&#34;默认&#34; EnumTagStrTag的实例,因此客户端代码将继承&#34;继承&#34;那些功能(阅读)很简单,只有实例化EnumTagStrTag

1 个答案:

答案 0 :(得分:3)

重叠实例非常脆弱。可以说,永远不应该使用它们。

无论如何,重叠的实例会导致GHC过早地避免提交实例。 E.g。

-- overlapping
instance ... => Read [Int]  -- 1
instance ... => Read [a]    -- 2

当GHC必须解决诸如Read [b]之类的约束时,它不会提交到实例2,因为它不能排除更具体的实例1:在所有类型变量{{1}之后以后可能会发现b

如果稍后发现Intb,则会选择实例1。如果发现Intb,则GHC将选择实例2。

然而,这要求有一个最好的选择&#34;在每种情况下。即当String时,两个实例都可以使用,但是1更具体,因此它是最好的#34; GHC选择它。

如果您有实例

b = Int

然后没有最好的实例存在,因为它们使用相同的头instance ... => Read [a] -- 3 instance ... => Read [a] -- 4 ,使得选择本质上不明确,GHC拒绝它。

可能让您感到困惑的是,GHC将从不考虑Read [a]左侧的内容(&#34; context&#34;)来选择一个实例。一旦你考虑它们的上下文,实例是否会重叠并不重要,因为没有类型可以同时满足3和4的上下文。检测上下文是互斥的是一般的非常困难,而且GHC甚至都没有尝试这样做。仅考虑实例头(=>右侧)。

除非客户端定义更具体的实例,否则无法创建自动打开的两个默认实例。

我更喜欢在实例中避免重叠,并在实例外部提供默认实现。 E.g。

=>

如果客户想要使用它,他们可以使用

启用默认实现
-- library
readPrec_fromStrTag :: StrTag a => Int -> ReadS a
readPrec_fromStrTag = parens $ do
    RP.lift P.skipSpaces
    (RP.lift $ expectShown anyStr) <++ RP.lift g
    where g = do
            Just s@(_:_) <- L.stripPrefix tagPrefix <$> expectTag
            return $ toStr s

如果这个和其他样板文件变得太麻烦,模板Haskell可能会帮助用户写出来。