我想知道 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
为什么会这样?
我想知道 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
为什么会这样?
我将逐步回答这个问题。让我们从=
等价谓词开始。谓词用于检查两个=
数是否相等。如果您提供除数字以外的任何其他内容,则会引发错误:
(= 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
一般来说:
=
当您希望测试两个数字是否相等时,请使用谓词。eqv?
当您希望测试两个非数字值是否相等时,请使用谓词。equal?
当您希望测试两个列表、向量等是否等价时,请使用谓词。eq?
除非您确切知道自己在做什么,否则不要使用谓词。RnRS 规范中有整整两页与eq?, eqv?, equal? and =
. 这是R7RS 规范草案。一探究竟!
解释:
=
比较数字,2.5 和 2.5 在数字上相等。equal?
对于减少到 的数字=
,2.5 和 2.5 在数值上相等。eq?
比较“指针”。在您的 Scheme 实现中,数字 5 被实现为“立即”(可能),因此 5 和 5 是相同的。数字 2.5 可能需要在您的 Scheme 实现中分配“浮点记录”,这两个指针不相同。eq?
是#t
当它是相同的地址/对象时。通常人们可以期望 #t 表示相同的符号、布尔值和对象,而 #f 表示具有不同类型、具有不同值或不同结构的值Scheme/Lisp 实现具有将类型嵌入其指针并嵌入的传统如果空间足够,则将值放在同一空间中。因此,某些指针实际上不是地址而是值,例如 charR
或 Fixnum 10
。这些将是eq?
因为“地址”是嵌入式类型+值。一些实现还重用不可变常量。(eq? '(1 2 3) '(1 2 3)) 在解释时可能是#f,但在编译时可能是#t,因为它可能获得相同的地址。(就像 Java 中的常量字符串池)。正因为如此,许多表达式涉及eq?
未指定,因此它评估为 #t 还是 #f 取决于实现。
eqv?
#t 与eq?
. 如果它是一个数字或字符并且它的值是相同的,它也是 #t,即使数据太大而无法放入指针。因此,对于那些eqv?
检查类型是受支持的类型之一的额外工作,它们都是相同的类型并且它的目标对象具有相同的数据值。
equal?
是#t,eqv?
如果它是像pair、vector、string和bytevector这样的复合类型,它会递归地equal?
处理这些部分。实际上,如果两个对象看起来相同,它将返回 #t。在 R6RS 之前,equal?
在圆形结构上使用是不安全的。
=
就像eqv?
,但它只适用于数字类型。它可能更有效。
string=?
就像equal?
,但它只适用于字符串。它可能更有效。
equal?
递归比较两个对象(任何类型)是否相等。
请注意,这对于大型数据结构可能会很昂贵,因为可能必须遍历整个列表、字符串、向量等。
如果对象仅包含单个元素(例如:数字、字符等),则与eqv?
.
eqv?
测试两个对象以确定两者是否“通常被视为同一对象”。
eqv?
并且eq?
是非常相似的操作,它们之间的差异将在某种程度上是特定于实现的。eq?
是相同的,eqv?
但可能能够辨别更精细的区别,并且可以更有效地实施。
eqv?
.=
比较数字是否相等。
(= 1 1.0 1/1 2/2)
您没有提到方案实现,但在 Racket 中,eq?
仅当参数引用同一对象时才返回 true。您的第二个示例是产生 #f ,因为系统正在为每个参数创建一个新的浮点数;它们不是同一个对象。
equal?
并且=
正在检查值等价,但=
仅适用于数字。
如果您使用的是 Racket,请在此处查看更多信息。否则,请检查您的方案实施的文档。
认为eq?
是指针相等。报告的作者希望它尽可能通用,因此他们不会直接这么说,因为它依赖于实现,并且说它会支持基于指针的实现。但他们确实说
通常可以实现 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=?
是字符串,细节是在报告中。
除了前面的答案,我将添加一些评论。
所有这些谓词都想identity
为一个对象定义抽象函数,但在不同的上下文中。
EQ?
是依赖于实现的,它are 2 objects the same?
仅在有限的使用中才回答这个问题。从实现的角度来看,这个谓词只是比较 2 个数字(指向对象的指针),它不查看对象的内容。因此,例如,如果您的实现没有唯一地保留字符串,而是为每个字符串分配不同的内存,那么(eq? "a" "a")
将是错误的。
EQV?
- 这看起来在对象内部,但用途有限。如果它返回 true 则取决于实现(eqv? (lambda(x) x) (lambda(x) x))
。在这里,如何定义这个谓词是一个完整的哲学,正如我们现在所知,有一些快速的方法可以比较某些函数的功能,但用途有限。但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 函数类),该语言也不会强制任何谓词为真或假。它会使语言的定义和实现更加复杂。
其他谓词的上下文更容易分析。