Show 的重叠实例

Overlapping instance for Show

假设我们有以下内容:

{-# LANGUAGE FlexibleInstances #-}

module Sample where

newtype A a =
  A a
  deriving (Show)

newtype L a =
  L [a]

class ListContainer l where
  getList :: l a -> [a]

instance ListContainer L where
  getList (L l) = l

instance (Show a, ListContainer l) => Show (l a) where
  show = const "example"

对于这段代码,ghc 会报错:

warning: [-Wdeferred-type-errors]
• Overlapping instances for Show (A a)
    arising from a use of ‘GHC.Show.$dmshowList’
  Matching instances:
    instance (Show a, ListContainer l) => Show (l a)
      -- Defined at /.../src/Sample.hs:18:10
    instance Show a => Show (A a)
      -- Defined at /.../src/Sample.hs:7:13
• In the expression: GHC.Show.$dmshowList @(A a)
  In an equation for ‘showList’:
      showList = GHC.Show.$dmshowList @(A a)
  When typechecking the code for ‘showList’
    in a derived instance for ‘Show (A a)’:
    To see the code I am typechecking, use -ddump-deriv
  In the instance declaration for ‘Show (A a)’
warning: [-Wdeferred-type-errors]
• Overlapping instances for Show (A a)
    arising from a use of ‘GHC.Show.$dmshow’
  Matching instances:
    instance (Show a, ListContainer l) => Show (l a)
      -- Defined at /.../src/Sample.hs:18:10
    instance Show a => Show (A a)
      -- Defined at /.../src/Sample.hs:7:13
• In the expression: GHC.Show.$dmshow @(A a)
  In an equation for ‘show’: show = GHC.Show.$dmshow @(A a)
  When typechecking the code for ‘show’
    in a derived instance for ‘Show (A a)’:
    To see the code I am typechecking, use -ddump-deriv
  In the instance declaration for ‘Show (A a)’

我可以理解它认为类型a可以派生Show,或者派生ListContainer,这可能导致Show

我们如何避免这种情况?

我知道有一个函数showList,但是它的签名有点外国。我已经有了一个函数,我打算用它来显示某些列表,直接 returns String

在您的示例中,您只需删除 instance (Show a, ListContainer l) => Show (l a) 并将 deriving (Show) 添加到 L 定义。

或者您可以从 A 定义中删除 deriving (Show)

如果您希望代码按现在的方式运行,请移除 deriving (Show) 并显式实现它

 instance {-# OVERLAPPING #-}  Show a => Show (A a)
      where 
         show (A a) = "A " ++ show a

I can understand that it thinks type a can either derive Show, or derive ListContainer, which may result in Show.

这不是它的想法。

当Haskell选择class实例时,它根本不考虑实例约束。选择实例时,它只考虑实例头(紧跟在 class 名称之后的东西)。

在您的 Show 实例中,实例头是 l a。此实例头匹配 A a(假设 l = A)。顺便说一下,它还匹配很多其他东西 - 例如,它匹配 Maybe a(其中 l = Maybe)和 Either b al = Either b)和 Identity a, 和 IO a - 几乎每个类型都有一个类型参数,想一想。 AMaybeIO 都没有 ListContainer 的实例并不重要,因为就像我上面说的,Haskell 不看选择实例时的约束,仅在实例头。

只有 找到一个匹配的实例(通过匹配它的头部)后,Haskell 才会检查该实例的约束是否确实得到满足。如果不是,他们会抱怨。但它永远不会返回并尝试选择另一个实例。

所以回到你的例子:因为 A 现在有两个匹配的 Show 实例 - 它自己的派生实例和你编写的 Show (l a) 实例, - 编译器抱怨说它们重叠。