16

或者更确切地说,为什么不能(==)在每种数据类型上使用?为什么我们必须推导出我们自己Eq?在其他语言中,例如 Python、C++,当然还有其他语言,它对所有内容都有默认实现!我想不出无法比较的类型。

4

7 回答 7

35

在 Python 中,默认的相等实现比较的是标识,而不是值。这对于用户定义的类很有用,默认情况下这些类是可变的,并且不必具有明确定义的“值”概念。is但即使在这种情况下,使用运算符直接比较可变对象的身份更为正常。

由于 Haskell 的不变性和共享这种“身份”的概念没有多大意义。如果您可以按身份比较两个术语,则可以确定它们是否共享,但是通常是否共享可能共享的两个术语通常取决于实现,因此此类信息不应影响程序(除非您喜欢在不同编译器优化策略下改变其行为的程序)。

所以 Haskell 中的平等总是价值平等;它告诉您两个术语是否表示相同的值(不一定它们是否具有相同的结构;如果您使用无序列表实现一个集合,那么具有不同结构的两个列表可以表示同一个集合)。

几乎所有的内置类型都是Eq已经的成员;最大的例外是函数类型。函数值相等的唯一真正明智的概念是外延相等(它们是否为每个输入返回相同的输出)。很容易说我们将使用它并让编译器访问函数定义的表示来计算它,但不幸的是,确定两个任意算法(此处以 Haskell 语法编码)是否总是产生相同的输出是一个已知的不可计算的问题;如果编译器真的能做到这一点,它就可以解决停机问题,我们就不必忍受底部值是每种类型的成员。

不幸的是,函数不能成为成员的事实Eq意味着许多其他东西也不能成为成员。可以比较整数列表是否相等,但函数列表不能,当它包含函数时,对于所有其他 conatiner-ish 类型也是如此。这也适用于您编写的 ADT,除非您可以为不依赖于所包含函数的相等性的类型定义一个合理的相等概念(也许该函数只是实现中的一种方便,以及哪个函数它不会影响您使用 ADT 表示的值)。

因此,有 (1) 类型已经是 的成员Eq,(2) 类型不能是 的成员Eq,(3) 可以Eq明显地成为成员的类型,(4)可以是成员的类型Eq但仅以非显而易见的方式,以及 (5) 可以以Eq明显方式成为成员的类型,但程序员更喜欢另一种方式。我认为 Haskell 处理这些情况的方式实际上是正确的方式。(1) 和 (2) 不需要您提供任何东西,并且 (4) 和 (5) 总是需要明确的实例声明。编译器可以为您提供更多帮助的唯一情况是 (3),它可能会为您节省 12 个打字字符(如果您已经是deriving其他字符,则为 4 个字符)。

我认为这将是一个相当小的成本胜利。编译器必须尝试构造所有事物的实例,并假定任何失败的事物都不应该有Eq实例。目前,如果您想派生一个Eq实例并且不小心编写了一个不起作用的类型,编译器会立即告诉您存在问题。使用建议的“隐式地尽你所能”策略,在您使用假定实例Eq时,此错误将显示为无法解释的“无Eq实例”错误。这也意味着,如果我将类型视为表示不存在合理等式关系的值简单的结构相等(上面的情况(5);还记得由无序列表表示的集合吗?),并且我忘记编写自己的Eq实例,那么编译器可能会自动为我生成错误 Eq的实例。Eq当我去使用它时,我宁愿被告知“你还没有编写实例”,而不是让程序在编译器引入的错误的情况下编译和运行!

于 2012-06-09T00:30:23.943 回答
20
于 2012-06-08T21:55:43.237 回答
11

您可能不想派生 Eq - 您可能想编写自己的实例。

例如,想象一个二叉树数据结构中的数据:

data Tree a = Branch (Tree a) (Tree a)
            | Leaf a

您的Leafs 中可以有相同的数据,但平衡不同。例如:

balanced = Branch (Branch (Leaf 1) 
                          (Leaf 2)) 
                  (Branch (Leaf 3) 
                          (Leaf 4))

unbalanced = Branch (Branch (Branch (Leaf 1) 
                                    (Leaf 2)) 
                            (Leaf 3)) 
                    (Leaf 4)

shuffled = Branch (Branch (Leaf 4) 
                          (Leaf 2)) 
                  (Branch (Leaf 3) 
                          (Leaf 1))

数据存储在树中的事实可能只是为了提高遍历效率,在这种情况下,您可能想说balanced == unbalanced. 你甚至可能想说balanced == shuffled

于 2012-06-10T03:25:01.087 回答
7

我想不出无法比较的类型。

let infiniteLoop = infiniteLoop

let iSolvedTheHaltingProblem f = f == infiniteLoop
-- Oops!
于 2012-06-09T04:41:31.370 回答
4

考虑以下 Python 示例:

>>> 2 == 2
True
>> {} == {}
True
>>> set() == set()
True
>>> [1,2,3] == [1,2,3]
True
>>> (lambda x: x) == (lambda x: x)
False

错误的?o_O 如果您意识到 Python == 比较指针值,这当然是有道理的,除非它不比较。

>>> f = (lambda x: x)
>>> f == f
True

Haskell 鼓励==始终表示结构相等(如果您使用deriving Eq.Eq存储函数的数据结构不能是Eq.

于 2012-06-09T09:30:49.323 回答
4

你如何比较功能?还是存在类型?还是 MVar?

有无与伦比的类型。


编辑:MVar 在 Eq 中!

instance Eq (MVar a) where
        (MVar mvar1#) == (MVar mvar2#) = sameMVar# mvar1# mvar2#

但它需要一个神奇的primop才能做到这一点。

于 2012-06-08T23:33:53.137 回答
4

因为比较值的方式可能是自定义的。例如,某些“字段”可能会被排除在比较之外。

或者考虑一种表示不区分大小写的字符串的类型。这种类型不想比较它包含的字符的身份。

于 2012-06-08T21:51:30.100 回答