首页 文章

如何为我自己的类型类中的所有类型声明类型类的实例(如Show)?

提问于
浏览
0

我有一个类型类:

class Wrapper w where
    open :: w -> Map String Int
    close :: Map String Int -> w

它看起来不是很有用,但是我用它来强烈地(不仅仅是 type 同义词)区分语义上不同的 Map String Int

newtype FlapMap = Flap (Map String Int)
newtype SnapMap = Snap (Map String Int)
...

并且仍然具有可在任何类型的类上运行的函数 .

  • 有没有更好的方法来做这种区分(可能没有 Wrapper 实例样板)?

我想做这个:

instance (Wrapper wrapper) => Show wrapper where
    show w = show $ toList $ open w

而不是编写许多样板 Show 实例 .

通过 FlexibleInstancesUndecidableInstances ,GHC引导我认为我的实例声明适用于所有内容,因为据称它与我的代码和 GHC.Show 中的其他 Show 实例冲突 . HaskellWiki和StackOverflow回答者和HaskellWiki说服我 OverlappingInstances 不是很安全,可能会让人困惑 . GHC甚至没有建议 .

  • 为什么GHC首先抱怨不知道要选择哪个fx Show Int 实例(为什么它不查看我在编译时给出的约束?)然后,被告知实例可能重叠,突然知道该怎么办?

  • 我可以避免在 newtype 中使用 OverlappingInstances 吗?

1 回答

  • 5

    如果没有 OverlappingInstances ,你就不能这样做,正如你所提到的,这是不可预测的 . 无论如何,它对你没有帮助,所以如果没有包装类型,你几乎无法做到这一点 .

    当然,这是相当不满意的,为什么会这样呢?正如您已经确定的那样,GHC在选择实例时不会查看实例上下文,只会查看实例头 . 为什么?好吧,请考虑以下代码:

    class Foo a where
      fooToString :: a -> String
    
    class Bar a where
      barToString :: a -> String
    
    data Something = Something
    
    instance Foo Something where
      fooToString _ = "foo something"
    
    instance Bar Something where
      barToString _ = "bar something"
    
    instance Foo a => Show a where
      show = fooToString
    
    instance Bar a => Show a where
      show = barToString
    

    如果单独考虑 FooBar 类型类,则上述定义是有意义的 . 任何实现 Foo 类型类的东西都应该“免费”获得一个 Show 实例 . 不幸的是, Bar 实例也是如此,所以现在你有 show Something 的两个有效实例 .

    由于类型类总是打开的(事实上,如果你能够为它定义自己的实例, Show 必须是打开的),所以不可能知道某人不会出现并添加他们自己的类似实例,然后在你的数据类型上创建一个实例,造成歧义 . 这实际上是来自类型形式的OO多重继承的经典diamond problem .

    您可以获得的最好的方法是创建一个提供相关实例的包装器类型:

    {-# LANGUAGE ExistentialQuantification #-}
    
    data ShowableWrapper = forall w. Wrapper w => ShowableWrapper w
    
    instance Show ShowableWrapper where
      show (ShowableWrapper w) = show . toList $ open w
    

    但是,在那时,你真的没有比仅仅编写自己的 showWrapper :: Wrapper w => w -> String 功能更有优势 .

相关问题