首页 > 解决方案 > 似乎不重叠的重叠实例

问题描述

考虑以下(接近最小)示例:

{-# 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 决定使用哪个实例而不受实例约束的结果,如此所述。不过,这个技巧在这里似乎不起作用:

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

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

标签: haskellderivingvia

解决方案


问题是这些实例确实重叠,因为实例解析机制在决定采用哪个实例时只查看实例头,并且仅在稍后选择实例,它才会检查约束以查看是否满足(并且否则抛出和错误)。

我建议阅读有关实例解析的文档

解决问题的一种方法(除了重新设计解决方案,这可能是正确的做法)是告诉 GHC 某个实例“不太重要”(或可重叠)。
这基本上意味着 GHC 将选择一个更具体的实例(如果它可用)(更具体的意思是您可以在上面链接的文档中阅读)。
这是通过使用 pragma {-# OVERLAPPABLE #-}or来实现的{-# 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

正如预期的那样。


推荐阅读