【发布时间】:2015-02-17 21:01:48
【问题描述】:
在this talk around the 1:20 mark 中,Edward Kmett 提到 Haskell 中缺少“类型类回溯”。考虑在列表上实现的“集合相等”(忽略顺序和多重性)的问题:
equals :: [a] -> [a] -> Bool
由于类型类的性质,如果我们只有 Eq a,我无法提供低效的 O(n²) 比较,但如果我们有 Ord a,则可以有效地比较 O(n log n) 中的列表。
我确实明白为什么这样的设施会有问题。同时,Edward 说,有一些“你可以玩的技巧”提到实例类型族。
因此我的问题是,实现相同效果的解决方法是什么:
- 提供了一个(低效的)默认实现
- 如果用户可以提供有关该类型的一些额外信息,我们会“解锁”更高效的实现
此解决方法不一定需要使用类型类。
编辑:有些人建议简单地提供equals 和efficientEquals 作为两种不同的方法。总的来说,我同意这是更符合 Haskell 习惯的方法。但是,我不相信这总是可能的。例如,如果上面的 equals 方法本身是类型类的一部分会怎样:
class SetLike s where
equals :: Eq a => s a -> s a -> Bool
假设这个类是由其他人提供的,所以我不能简单地向类型类添加一个新函数。我现在想为[] 定义实例。我知道[] 始终可以提供equals 的实现,无论a 有什么限制,但如果有更多关于a 的信息可用,我无法告诉我的实例使用更高效的版本。
也许我可以将列表包装在一个新类型中并用一些额外的类型信息标记它?
【问题讨论】:
-
这很有趣,我不知道答案。但是一个快速而肮脏的解决方案是定义一个不同的函数,比如
efficientEquals :: (Ord a) => [a] -> [a] -> Bool和equals :: (Eq a) => [a] -> [a] -> Bool。 -
请明确表示我们正在讨论比较列表不考虑顺序(也可能是多重性)。显然
(==)不需要列表上的 O(n^2)。 -
有一些技术使用
OverlappingInstances,或封闭类型族,在某些情况下可用于获得您正在寻找的东西(虽然不是真的在这里;除非您同意,比如说,用你关心的Ord实例枚举/复制所有类型)。请参阅the "advanced overlap" 维基页面。 "extra method trick" 也可能与此处相关。 -
这里的主要问题是 Eq 是 Ord 的超类。所以 every Ord 类型也是 Eq 类型,这意味着您希望仅在
Ord anot 成立的情况下选择 Eq 情况。 Haskell 中的类型类系统经过严格设计,以确保它永远不会提交否定的约束;这个属性是它提供的许多一致性保证的核心,因为通过单独编译模块,GHC 永远无法区分“不存在实例”和“没有实例在范围内”。 -
我能想到的方法可以让您想出一个适用于 Eq 和 Ord 实例的
equals,但我想不出任何方法不仅会抱怨模棱两可的类型变量当调用事物时是 Ord 的实例。您最终不得不在每个呼叫站点手动说(equals :: Ord a => [a] -> [a] -> Bool) xs ys。而且,如果您愿意手动声明要调用的 equals 版本,它们也可能具有不同的名称,这样更容易做到。