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

gat*_*ado 5 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
Run Code Online (Sandbox Code Playgroud)

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

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

第二个代码路径可能更好,因为它使用Int64s 执行散列.我可以指定使用实例Hashable String的这个派生吗?

编辑1

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

instance Hashable String where
    hash x = hash (hash64 x)
Run Code Online (Sandbox Code Playgroud)

我明白了

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

编辑2

欢迎任何其他解决此特定问题的方案.一个好的解决方案可以提供对这个重叠实例问题的洞察

C. *_*ann 6

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

在这种情况下,你有一个或多个专门的实例和一个单独的catch-all instance Foo a作为"默认",通常效果很好.

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

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

  • 实例的情况下被忽略,直到后,它已经选定,所以不要尝试实例这种方式来区分; 有解决方法,但他们很烦人.

例如,如果你有一个不是某个实例的某种类型的列表,那么后一点在这里是相关的Hashable64; GHC将选择更具体的第二个实例,然后由于上下文而失败,即使完整类型(列表,而不是元素类型)实例,Hashable64因此可以使用第一个通用实例.


编辑:哦,我明白了,我稍微误解了一下情况,关于实例的来源.GHC用户指南:

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

(......)

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

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

...换句话说,只有在启用较少特定实例的情况下才允许重叠OverlappingInstances,实例Hashable [a]不是.所以你的实例Hashable a是允许的,但Hashable [Char]正如所观察到的那样,失败了.

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

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

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

  • 类型包装器:newtype包装器是消除实例歧义的"标准"方式(cf Monoid).通过在值周围使用这样的包装器,您将能够编写任何您喜欢的实例,因为没有任何预定义的实例匹配.这成为问题,如果你有很多的,将需要包装/解开了新人类,但请记住,您可以定义其他实例功能(例如Num,Show用于包装等),容易和有在运行时间上的开销.

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

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

  • @gatoatigrado:对不起我帮不了多忙.你可以使用一些技术为`newtype`包装器稍微平滑一下 - 例如,为了包装像`newtype WrapString = WS String`这样的特定类型,你可以使用`GeneralizedNewtypeDeriving`来创建有用的实例,以及用于通用包装器比如`newtype Wrap a = W a`你有'Functor`和`Applicative`的简单实例,可以让你通过包装器解除功能. (2认同)