Haskell 似乎不重叠的重叠实例

Haskell 似乎不重叠的重叠实例,haskell,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

考虑以下接近最小的示例:

{-# 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结果,因为我们有实例谓词Bar和谓词a=>Predicate Baz a

但GHC 8.6.3和8.0.1都拒绝这一点:

测试。hs:18:16:错误: •谓词Baz Bar的重叠实例 因使用“测试”而产生 匹配实例: 实例谓词a,可遍历的t=>谓词TA -在试验中定义。hs:6:10 实例谓词a=>谓词Baz a -在试验中定义。hs:14:10 •在“$”的第二个参数中,即“test$Baz Bar” 在表达式中:print$test$Baz Bar 在“main”的公式中:main=print$test$Baz条 | 18 | main=打印$test$Baz条 | ^^^^^^^^^^^^^^ 但是没有重叠:我们可以通过注释掉谓词Baz a instance来确认没有可遍历Baz的实例,在这种情况下,我们会得到错误:

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

给出了一个重复的实例声明错误,因此我将问题留给一个在这种情况下有效的解决方案。

问题是这些实例确实重叠,因为实例解析机制在决定采用哪个实例时只会查看实例头,只有在选择了一个实例之后,它检查约束是否满足,否则抛出错误

我建议您阅读有关的文档

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

正如预期的那样。

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

我建议您阅读有关的文档

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