为什么Haskell中的Eq不是每个类型的一部分?

时间:2012-06-08 21:48:50

标签: haskell typeclass

或者更确切地说,为什么(==)不适用于每种数据类型?为什么我们必须得出Eq leleves?在其他语言中,例如Python,C ++,当然还有其他语言,它有一个默认的实现! 我想不出无法比较的类型。

7 个答案:

答案 0 :(得分:34)

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

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

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

几乎所有内置类型都已成为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实例”而不是编译程序并运行编译器引入的错误!

答案 1 :(得分:20)

你无法想象一个不可比的类型?嗯,经典的例子是函数。考虑函数[()]->Bool。当每个可能的输入返回相同的值时,两个这样的函数是相等的。但“不幸的是”,有无数的这样的列表:由于Haskell是懒惰的,列表大小甚至不受内存的约束。当然,你可以比较,每个列表输入的长度小于某个固定的lMax,但你会在哪里画线?在1000000000次相等的返回之后,不可能确定您比较的函数不会突然返回replicate 1000000001 ()的不同结果。因此(==) :: ([()]->Bool) -> ([()]->Bool) -> Bool实际上永远不会返回True,只有False(如果找到函数不同的输入)或(如果函数实际上相等)。但是你无法评估

答案 2 :(得分:11)

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

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

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

您可以在Leaf中获得相同的数据,但平衡方式不同。例如:

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

答案 3 :(得分:7)

  

我无法想到无法比较的类型。

let infiniteLoop = infiniteLoop

let iSolvedTheHaltingProblem f = f == infiniteLoop
-- Oops!

答案 4 :(得分:4)

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

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

答案 5 :(得分:4)

你如何比较功能?还是存在类型? MVar?有无可比拟的类型。


编辑:MVar在Eq!

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

但这需要一个神奇的元素才能实现。

答案 6 :(得分: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的实例。