方案中的eq?,eqv ?, equal?和=有什么区别?

时间:2013-04-30 11:52:28

标签: functional-programming scheme

我想知道这些操作之间的区别。我在Stack Overflow中看到了类似的问题,但它们是关于Lisp的,并且没有三个运算符之间的比较。所以如果已经提出这个问题,请告诉我。

我在Scheme中编写不同类型的命令,我得到以下输出:

(eq? 5 5) -->#t
(eq? 2.5 2.5) -->#f
(equal? 2.5 2.5) --> #t
(= 2.5 2.5) --> #t

有人可以解释为什么会这样吗?

7 个答案:

答案 0 :(得分:124)

我会逐渐回答这个问题。让我们从=等价谓词开始。 =谓词用于检查两个数字是否相等。如果您提供除了数字之外的任何其他内容,那么它将引发错误:

(= 2 3)     => #f
(= 2.5 2.5) => #t
(= '() '()) => error

eq?谓词用于检查其两个参数是否与内存中的同一对象相同。例如:

(define x '(2 3))
(define y '(2 3))
(eq? x y)         => #f
(define y x)
(eq? x y)         => #t

但请注意,内存中只有一个空列表'()(实际上内存中不存在空列表,但是指向内存位置0的指针被视为空列表)。因此,在比较空列表时eq?将始终返回#t(因为它们代表内存中的同一对象):

(define x '())
(define y '())
(eq? x y)      => #t

现在取决于实现eq?可能会或可能不会返回#t原始值,例如数字,字符串等。例如:

(eq? 2 2)     => depends upon the implementation
(eq? "a" "a") => depends upon the implementation

这是eqv?谓词出现的地方。 eqv?eq?谓词完全相同,只是它会始终返回#t相同的原始值。例如:

(eqv? 2 2)     => #t
(eqv? "a" "a") => depends upon the implementation

因此eqv?eq?的超集,在大多数情况下,您应该使用eqv?代替eq?

最后我们来到equal?谓词。 equal?谓词与eqv?谓词完全相同,不同之处在于它还可用于测试两个列表,向量等是否具有满足eqv?谓词的对应元素。例如:

(define x '(2 3))
(define y '(2 3))
(equal? x y)      => #t
(eqv? x y)        => #f

一般来说:

  1. 当您希望测试两个数字是否相同时,请使用=谓词。
  2. 当您希望测试两个非数字值是否相同时,请使用eqv?谓词。
  3. 当您希望测试两个列表,向量等是否相同时,请使用equal?谓词。
  4. 除非您确切知道自己在做什么,否则请勿使用eq?谓词。

答案 1 :(得分:12)

RNRS规范中有两页与eq?, eqv?, equal? and =相关。这是Draft R7RS Specification。看看吧!

说明:

  • =比较数字,2.5和2.5在数值上相等。
  • equal?数字减少到=,2.5和2.5在数值上相等。
  • eq?比较'指针'。在Scheme实现中,数字5实现为“立即”(可能),因此5和5是相同的。数字2.5可能需要在Scheme实现中分配“浮点记录”,这两个指针不相同。

答案 2 :(得分:7)

eq? #t,当它是相同的地址/对象时。 通常人们可以期待#t用于相同的符号,布尔和对象以及#f用于不同类型的值,具有不同的值或不同的结构 Scheme / Lisp-implementation具有嵌入的传统如果它有足够的空间,则键入其指针并将值嵌入到同一空间中。因此,一些指针实际上不是地址而是值,例如char R或Fixnum 10。这些将是eq?,因为"地址"是嵌入式+值。一些实现还重用不可变常量。 (eq?'(1 2 3)'(1 2 3))在解释时可能是#f但在编译时可能是#t,因为它可能得到相同的地址。 (与Java中的常量String池一样)。因此,许多涉及eq?的表达式未被指定,因此评估为#t或#f是依赖于实现的。

eqv? #t与eq?相同。它也是#t,如果它是一个数字或字符,它的值是相同的,即使数据太大而不适合指针。因此,对于那些eqv?,检查该类型的额外工作是受支持的,两者都是相同的类型,并且它的目标对象具有相同的数据值。

equal? eqv?相同的是#t,如果它是像pair,vector这样的复合类型, string和bytevector以递归方式对部分进行equal?在实践中,如果两个对象看起来相同,它将返回#t。在R6RS之前,在圆形结构上使用equal?是不安全的。

= eqv?类似,但仅适用于数字类型。它可能更有效率。

string=? equal?类似,但仅适用于字符串。可能效率更高。

答案 3 :(得分:6)

equal?以递归方式比较两个对象(任何类型)的相等性。

  • 请注意,对于大型数据结构而言,这可能很昂贵,因为必须遍历整个列表,字符串,向量等。

  • 如果对象只包含一个元素(EG:数字,字符等),则与eqv?相同。


eqv?测试两个对象以确定两者是否“通常被视为同一个对象”。

  • eqv?eq?是非常相似的操作,它们之间的差异将在某种程度上与实现有关。

eq?eqv?相同,但可以识别更精细的区别,并且可以更有效地实施。

  • 根据规范,这可以实现为快速有效的指针比较,而不是eqv?的更复杂的操作。

<小时/> =比较数字相等的数字。

  • 请注意,可以提供两个以上的数字,例如:(= 1 1.0 1/1 2/2)

答案 4 :(得分:4)

您没有提到方案实现,但在Racket中,eq?仅在参数引用同一对象时才返回true。你的第二个例子是产生#f,因为系统正在为每个参数创建一个新的浮点数;他们不是同一个对象。

equal?=正在检查价值等值,但=仅适用于数字。

如果您使用的是Racket,请查看here以获取更多信息。否则,请查看方案实施的文档。

答案 5 :(得分:3)

eq?视为指针相等。 Report的作者希望它尽可能通用,所以他们不会直截了当地说这是因为它依赖于实现,并且说它会支持基于指针的实现。但他们确实说了

  

通常可以实现eq?比eqv更有效率,例如,作为一个简单的指针比较

这就是我的意思。保证(eqv? 2 2)返回#t(eq? 2 2)未指定。现在想象一下基于指针的实现。在其中eq?只是指针比较。由于(eq? 2 2)未指定,这意味着此实现可以自由地创建从源代码读取的每个新数字的新内存对象表示。 eqv?必须实际检查其论点。

OTOH (eq 'a 'a)#t。这意味着此类实现必须识别具有重复名称的符号,并在内存中为所有这些使用相同的一个表示对象。

假设一个实现不是基于指针的。只要它遵守报告,就没关系。作者只是不希望被视为规定实现者的具体实现,因此他们会仔细选择他们的措辞。

无论如何,这是我的猜测。

非常粗略地说,eq?是指针相等,eqv?是(原子)值感知,equal?也是结构感知的(递归地检查它的参数,所以最后(equal? '(a) '(a))必须为#t),=代表数字,string=?代表字符串,详细信息位于报告中。

答案 6 :(得分:0)

除了先前的答案,我还会添加一些评论。

所有这些谓词都希望为对象定义identity的抽象功能,但是要在不同的上下文中使用

EQ?依赖于实现,并且仅在有限的使用中不能回答问题are 2 objects the same?。从实现的角度来看,该谓词仅比较2个数字(指向对象的指针),而不查看对象的内容。因此,例如,如果您的实现未将字符串唯一地保留在内部,而是为每个字符串分配了不同的内存,则(eq? "a" "a")将为false。

EQV?-看起来在对象内部,但用途有限。如果它为(eqv? (lambda(x) x) (lambda(x) x))返回true,则取决于实现。这是定义该谓词的完整哲学,因为我们今天知道,有一些快速方法可以比较某些功能的功能,而使用范围有限。但是eqv?为大数字,字符串等提供了连贯的答案。

实际上,其中一些谓词尝试使用对象的抽象定义(数学上),而其他谓词则使用对象的表示形式(如何在真实机器上实现)。身份的数学定义来自莱布尼兹,它说:

X = Y  iff  for any P, P(X) = P(Y)
X, Y being objects and
P being any property associated with object X and Y.

理想情况下,应该能够在计算机上实现此定义,但是出于不确定性和/或速度的原因,它并不是从字面上实现的。这就是为什么有很多运营商尝试每个运营商专注于围绕此定义的不同观点的原因。

尝试想象一个延续的身份的抽象定义。即使您可以提供函数子集(sigma-recursive class of functions)的定义,该语言也不会强加任何谓词为true或false。这将使语言的定义和实现更加复杂。

其他谓词的上下文更易于分析。