Haskell - 如何指定类型类实例?

时间:2011-05-31 22:53:34

标签: haskell instance typeclass

我有一个(相当)合法的情况,其中有两个类型实例实现,我想指定一个默认实例。在注意到使用Int类型的模块化算法导致大量的哈希冲突之后,我想尝试GHC的Int64。我有以下代码:

class Hashable64 a where
    hash64 :: a -> Int64

instance Hashable64 a => Hashable a where
    hash = fromInteger . toInteger . hash64

instance Hashable64 a => Hashable64 [a] where
    hash64 = foldl1 (\x y -> x + 22636946317 * y) . map hash64

和一个实例Hashable64 Char,从而导致Hashable String的两个实现,即:

  • Data.Hashable中定义的那个。
  • 注意到它是Hashable64个实例,然后转换为Int实例的常规Data.Hashable

第二个代码路径可能更好,因为它使用Int64执行散列。 我是否可以指定使用实例Hashable String

的这种派生

编辑1

抱歉,我忘了添加我已经尝试了重叠实例的事情;也许我只是没有正确实现它?重叠实例的文档说,当一个实例更具体时,它可以工作。但是当我尝试为Hashable String添加特定实例时,情况并没有改善。 [http://pastebin.com/9fP6LUX2]的完整代码(对于多余的默认标头抱歉)。

instance Hashable String where
    hash x = hash (hash64 x)

我得到了

Matching instances:
  instance (Hashable a) => Hashable [a] -- Defined in Data.Hashable
  instance [overlap ok] Hashable String
    -- Defined at Hashable64.hs:70:9-23

编辑2

欢迎任何其他解决此特定问题的方案。一个好的解决方案可能会提供对此重叠实例问题的深入了解。

1 个答案:

答案 0 :(得分:6)

这种情况由GHC's OverlappingInstances extension处理。粗略地说,这个扩展允许实例共存,尽管存在一些可以应用的类型。对于这样的类型,GHC将选择“最具体”的实例,在某些情况下这有点模糊,但通常会做你想要的。

这种情况下,你有一个或多个专门的实例和一个全能instance Foo a作为“默认”,通常效果很好。

重要实例需要注意的主要障碍是:

  • 如果有什么东西强迫GHC选择一个不明确的多态类型的实例,它会拒绝可能有神秘的编译器错误

  • 选择之后,实例的上下文将被忽略,因此不要试图区分实例;有解决方法,但它们很烦人。

如果您有一个不是Hashable64实例的某种类型的列表,那么后一点与此相关: GHC将选择更具体的第二个实例,然后因为上下文而失败,即使完整类型(列表,而不是元素类型) Hashable64的实例,因此也可以工作与第一个通用实例。


编辑:哦,我明白了,我对这个实例来自何处的情况略有误解。 GHC用户指南:

  

重叠或不连贯的意愿是实例声明本身(...)的属性。导入和使用实例声明的模块中不需要任何标志。

     

(...)

     

这些规则使图书馆作者可以设计一个依赖于重叠实例的库,而不需要库客户端知道。

     

如果在没有-XOverlappingInstances的情况下编译实例声明,那么该实例永远不会重叠。这可能不方便。 (...)我们有兴趣收到有关这些要点的反馈意见。

...换句话说,只有在启用OverlappingInstances的情况下构建 less 特定实例时才允许重叠,Hashable [a]的实例不是。因此,Hashable a的实例是允许的,但Hashable [Char]的实例会失败,正如所观察到的那样。

这是为什么用户指南发现当前规则不能令人满意的一个简洁说明(其他规则会有自己的问题,所以不清楚最好的方法是什么,如果有的话)。

回到现在,你有多种选择,比你希望的方便一点。在我的头顶:

  • 备用类:定义等效的Hashable类,编写所需的重叠实例,并在上下文中使用Hashable的通用实例根据需要回到原来的。如果您正在使用另一个需要Hashable个实例的库,而不是预先散列的值或显式散列函数,则会出现问题。

  • 类型包装器newtype包装器是消除实例歧义的“标准”方式(c.f。Monoid)。通过在值周围使用这样的包装器,您将能够编写任何您喜欢的实例,因为没有任何预定义的实例匹配。如果您有许多需要打包/解包newtype的函数,这会成为问题,但请记住,您可以为其定义其他实例(例如NumShow等)容易包装,运行时没有开销。

还有其他更为神秘的解决方法,但我不能提供太多明确的指导,因为这是最不尴尬的,往往是非常依赖于情境。

值得注意的是,你肯定会推动使用类型类明智表达的内容的边缘,所以事情很尴尬并不奇怪。这不是一个非常令人满意的情况,但是当你被限制为为其他地方定义的类添加实例时,你几乎无能为力。