似乎没有重叠的重叠实例

时间:2019-01-09 00:01:52

标签: haskell

考虑以下(几乎是最少的)示例:

{-# Language FlexibleInstances #-}

class Predicate a where
    test :: a -> Bool

instance (Predicate a, Traversable t) => Predicate (t a) where
    test = all test

data Bar = Bar
instance Predicate Bar where
    test Bar = False

data Baz a = Baz a
instance Predicate a => Predicate (Baz a) where
    test (Baz x) = test x

main :: IO ()
main = print $ test $ Baz Bar

看看test $ Baz Bar,您会期望得到False的结果,因为我们有实例Predicate BarPredicate a => Predicate (Baz a)

但是GHC 8.6.3和8.0.1都拒绝这样做:

test.hs:18:16: error:
    • Overlapping instances for Predicate (Baz Bar)
        arising from a use of ‘test’
      Matching instances:
        instance (Predicate a, Traversable t) => Predicate (t a)
          -- Defined at test.hs:6:10
        instance Predicate a => Predicate (Baz a)
          -- Defined at test.hs:14:10
    • In the second argument of ‘($)’, namely ‘test $ Baz Bar’
      In the expression: print $ test $ Baz Bar
      In an equation for ‘main’: main = print $ test $ Baz Bar
   |
18 | main = print $ test $ Baz Bar
   |                ^^^^^^^^^^^^^^

而且没有重叠:我们可以通过注释掉Traversable Baz实例来确认没有Predicate (Baz a)实例,在这种情况下,我们会得到错误:

test.hs:18:16: error:
    • No instance for (Traversable Baz) arising from a use of ‘test’
    • In the second argument of ‘($)’, namely ‘test $ Baz Bar’
      In the expression: print $ test $ Baz Bar
      In an equation for ‘main’: main = print $ test $ Baz Bar
   |
18 | main = print $ test $ Baz Bar
   |                ^^^^^^^^^^^^^^

我假设这是FlexibleInstances的限制?如果是这样,为什么,并且有批准的解决方法?


好的,事实证明,这是GHC决定对哪个实例使用独立于实例约束的 的结果,如here所述。该技巧似乎在这里似乎不起作用:

instance (b ~ Baz, Predicate a) => Predicate (b a) where

给出一个Duplicate instance declarations错误,所以我将问题悬而未决,以寻求在这种情况下有效的解决方案。

1 个答案:

答案 0 :(得分:12)

问题是这些实例确实重叠,因为实例解析机制仅在决定采用哪个实例时才查看实例头,并且仅在选择实例后的 之后才进行检查。约束,以确保它得到满足(否则引发并出错)。

我建议阅读instance resolution上的文档

解决问题的一种方法(除了重新设计解决方案之外,这可能是正确的选择),是告诉GHC某个实例“不那么重要”(或可重叠)。
这基本上意味着GHC将选择一个更具体的实例(如果有的话)(更具体的意思是您可以阅读上面链接的文档)。
这可以通过使用杂注{-# OVERLAPPABLE #-}{-# OVERLAPS #-}来实现(请阅读文档以了解不同之处,基本上前者更为具体)。

结果代码如下所示

{-# Language FlexibleInstances #-}

class Predicate a where
    test :: a -> Bool

instance {-# OVERLAPPABLE #-} (Predicate a, Traversable t) => Predicate (t a) where
    test = all test

data Bar = Bar
instance Predicate Bar where
    test Bar = False

data Baz a = Baz a
instance Predicate a => Predicate (Baz a) where
    test (Baz x) = test x

main :: IO ()
main = do
   print . test $ Baz Bar
   print . test $ ([] :: [Bar])
   print . test $ [Bar]
   print . test $ Baz ([] :: [Bar])

运行结果为

False
True
False
True

符合预期。