问题 GHC重写规则专门用于类型类的函数


使用GHC RULES 编译,可以为特定类型专门化多态函数。 Haskell报告中的示例:

genericLookup :: Ord a => Table a b   -> a   -> b
intLookup     ::          Table Int b -> Int -> b

{-# RULES "genericLookup/Int" genericLookup = intLookup #-}

这将使GHC使用 intLookup 在整数索引表和其他通用版本,其中 intLookup 可能会更有效率。

我想用类似下面的(略微简化的)函数来完成类似的事情:

lookup    :: Eq a  => [(a, b)] -> a -> b
lookupOrd :: Ord a => [(a, b)] -> a -> b

哪里 lookupOrd 创造一个 Map 从输入列表然后使用 Map.lookup,这需要 a 成为。的成员 Ord

现在我想告诉GHC lookupOrd 应该用来代替 lookup 每当 a 确实是一个成员 Ord 类型类。但是,以下规则并未进行类型检查:

{-# RULES "lookup/Ord" lookup = lookupOrd #-}

GHC(理所当然地)抱怨它无法推断 (Ord a) 从上下文 (Eq a)。是否有重写规则允许我执行这种类型的基于类的专业化?


11708
2017-11-02 18:03


起源

这几乎是旧的 开放世界的问题:您试图根据类型是否属于某种类型来做出决定。 但是Haskell没有概念 不 在一个类型类!总是假设任何类型都可能在任何类中(即使还没有遇到过这样的实例,有人可能仍会在以后添加它)。 - leftaroundabout
@leftaround关于这个问题不是太严重;可以设想采用尽力而为的方法来应用这样一条规则(如果GHC首先支持它)。 - Joachim Breitner


答案:


虽然这是一个老问题,但未来的Google员工可能会有兴趣知道有办法做OP所需要的,使用 ifcxt 包。

您可以查看文档以获取更多示例,但基本上您将使用第二个示例, 示例2:使您的代码渐近有效,作为基础。

有了这两个功能,

lookup    :: Eq a  => [(a, b)] -> a -> b
lookupOrd :: Ord a => [(a, b)] -> a -> b

你会做的,

cxtLookup :: forall a. (Eq a, IfCxt (Ord a)) => [(a, b)] -> a -> b
cxtLookup = ifCxt (Proxy::Proxy (Ord a)) lookupOrd lookup

这将允许您根据数据结构实现的类型类选择最有效的函数。

请注意,我不知道它会对性能产生多大影响,但我认为与查找函数的运行时相比它是微不足道的,因此确实值得。


1
2018-01-12 16:15





我认为没有,并且有理由说明GHC目前的实施并不容易实现:

虽然您在Haskell语法中指定了规则,但它们将应用于GHC的核心语言。在那里,类型类约束已经变成了字典参数,所以函数

lookup :: Eq a => a -> [(a,b)] -> Maybe b

现在有类型

lookup :: forall a b. Eq a -> a -> [(a, b)] -> Maybe b

而你的 lookupOrd 会有类型

lookupOrd :: forall a b. Ord a -> a -> [(a, b)] -> Maybe b

哪里 Eq a 和 Ord a 已成为 普通数据类型。特别是在这个阶段,没有任何概念  再输入一个类型的类;所有这些都已经解决了。

所以现在假设编译器发现了一个

lookup (dict :: Eq MyType) (x :: MyType) (list :: [(MyType, String)])

它应该用什么代替呢?你告诉过他了 x 和 list 可以传递给 lookupOrd 同样,但该函数也需要类型的值 Ord MyType,这不会发生在规则的左侧。所以GHC不得不放弃。

像这样的规则

{-# RULES "lookup/Ord" forall a x. lookup a x = lookupOrd (a::()) x #-}

但是,这里有问题的论证( Ord 字典)已经在规则中修复,并且在应用规则时不需要找到。

原则上,其他编译器设计可能允许您需要的表单规则。


13
2017-11-03 00:11



如果你写,GHC 8.0.1不会抱怨 {-# RULES "lookup/Ord" forall (e :: Ord k => k) (l :: [(k,a)]) . lookup e l = lookupOrd e l #-},但规则似乎并没有实际开火。 - Jeremy List
如果GHC在类型检查后坚持其实例解析机制,那么面对GADT的这一点和专业化都可能变得易于处理。 - dfeuer