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

L01*_*man 16 haskell typeclass

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

Ben*_*Ben 34

在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当我使用它时,我宁愿被告知"你还没有编写实例",而不是编译程序编译并运行编译器引入的错误!

  • 第一个Haskell标准实际上规定,如果省略了数据类型的deriving子句,您将获得尽可能多的类派生.后来发生了变化,因为在某种程度上派生出来的程度有些不可预测. (4认同)

lef*_*out 20

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

  • 例如,考虑`heapSort,quickSort,bubbleSort :: [Int] - > [Int]`.它们在某种意义上都是相同的,对于任何给定的输入,它们每个都产生相同的输出.但它们是具有不同性能特征的不同实现.一个比较函数应该如何计算它们是否相等? (9认同)
  • 仅仅因为两个函数具有不同的实现,这并不意味着它们不相等. (7认同)
  • 递归数据也很难比较相等,除非你有额外的知识,可以让你避免无限循环... (4认同)
  • @ user102008:是的,它会*有用*,但它不是*与平等相同的东西.因此,为函数设置单独的"equalImplemtation"函数可能很有用; 使用`==`这不会. (4认同)
  • @ L01man(另外,因为没有其他人说过它:函数按类型`*`分类.) (3认同)
  • @ user102008:这样的东西*可能*有用,但它违反了引用透明性:Haskell中的核心原则是你可以用它返回的值替换函数调用而不改变程序的行为.在存在这样的函数的情况下,对代码进行更改会更加困难,同时确保没有任何内容会破坏.(旁注:只要你将自己局限于*total*函数,我相信`[()] - > Bool`上的相等实际上是可判定的.如果你需要`f(repeat())`非底部,`f `只能查看列表中有限的许多元素. (3认同)
  • @Veedrac:回到这四年(!)年后,我实际上不确定"参考透明度"是否属于我的意思.但是区分一个函数的实现与另一个函数的能力会违反抽象边界:你不能再重构一些内部函数,保持契约和类型相同,并确保客户端代码不会改变行为. (3认同)

ami*_*dfv 11

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

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

data Tree a = Branch (Tree a) (Tree a)
            | Leaf a
Run Code Online (Sandbox Code Playgroud)

您可以在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))
Run Code Online (Sandbox Code Playgroud)

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


Jör*_*tag 7

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

let infiniteLoop = infiniteLoop

let iSolvedTheHaltingProblem f = f == infiniteLoop
-- Oops!
Run Code Online (Sandbox Code Playgroud)

  • 男人,人们只是喜欢减少停止问题:P在这种情况下,我认为`==`返回,这是一个无限循环是完全合理的.就像你做了`[] ==过滤(不是.sumOfTwoPrimes)[2,4 ...]`(注意这个*做*typecheck) (2认同)

Dan*_*ton 5

考虑以下 Python 示例:

>>> 2 == 2
True
>> {} == {}
True
>>> set() == set()
True
>>> [1,2,3] == [1,2,3]
True
>>> (lambda x: x) == (lambda x: x)
False
Run Code Online (Sandbox Code Playgroud)

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

>>> f = (lambda x: x)
>>> f == f
True
Run Code Online (Sandbox Code Playgroud)

Haskell 鼓励始终==表示结构相等(如果您使用 ,则总是如此。由于没有人真正知道一种完全合理且易于处理的方法来确定两个函数在结构上是否等效,因此没有函数的实例。通过扩展,任何其中存储函数的数据结构不能是 的实例。deriving EqEqEq

  • 嗯,“Bool -> Bool”就是其中之一。一般来说,任何有限枚举类型到任何可等同类型都可以,尽管域必须很小才能实现实用:)但是,更多魔法是可能的 - 请参阅http://math.andrej.com/2007/09/ 28/看似不可能的功能程序/(即使我确实在不同的评论线程中滥用了它) (3认同)
  • 即使使用 Bool -> Bool,您也需要小心,因为它们在存在底部时的行为可能会有所不同。:/ (2认同)