【问题标题】:Overlapping instance for ShowShow 的重叠实例
【发布时间】:2019-09-20 16:44:57
【问题描述】:

假设我们有以下内容:

{-# 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,但它的签名有点陌生。我已经有一个函数,我打算用来显示某些列表,它直接返回String

【问题讨论】:

    标签: haskell overlapping-instances


    【解决方案1】:

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

    这不是它的想法。

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

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

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

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

    【讨论】:

    • 这似乎是不幸和不直观的行为,至少从用户的角度来看是这样。 ghc 以这种方式工作是否有充分的理由?
    • 如果实例是基于约束选择的,那么结果将取决于哪些实例在范围内,这将是超级脆弱的。例如,如果您添加一个看似无关的模块的导入,而该模块恰好包含一个实例,则您的程序的含义可能会发生变化。或者甚至不更改您的导入,而是升级依赖项。这将是地狱的调试。
    【解决方案2】:

    在您的示例中,您可以删除 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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-01-14
      • 1970-01-01
      相关资源
      最近更新 更多