所以我今天偶然发现了这件事,让我感到困惑。
(define (x) '(1))
(eq? (x) (x)) ;=> #t
(eq? '(1) '(1)) ;=> #f
(define (y) (list 1))
(eq? (y) (y)) ;=> #f
(eq? (list 1) (list 1)) ;=> #f
有人能解释一下这里发生了什么吗?
答案 0 :(得分:4)
编译此程序时
(define (x) '(1))
(eq? (x) (x))
(eq? '(1) '(1))
编译成(类似):
(define datum1 '(1))
(define datum2 '(1))
(define datum3 '(1))
(define (x) datum1)
(eq? (x) (x))
(eq? datum2 datum3)
因此(x)
将始终返回datum1
中存储的对象。
另一方面,表达式(eq? '(1) '(1))
将是
发现datum2
和datum3
不存储相同的对象。
注意:编译器编写器有一个选择。许多Scheme实现将上述程序编译为:
(define datum1 '(1))
(define (x) datum1)
(eq? (x) (x))
(eq? datum1 datum1)
然后在两种情况下结果都是正确的。
注意:quote
的文档没有明确说明程序中多次出现的'(1)
是否会产生相同的值。因此,此行为可能在将来发生变化。 [虽然我认为目前的行为是一个慎重的选择]
答案 1 :(得分:3)
eq?检查对象是否相同(想想"如果指针指向内存中的相同地址")。
在第一种情况下,您正在使用在编译时创建的文字。比较(和修改)文字通常是未定义的行为。这里看起来过程$('#c').on("mousedown click", function(){
cNote.currentTime = 0;
cNote.play();
});
每次返回相同的文字,但在第二个表达式中,它看起来像2个文字不一样。正如我所说,未定义的行为。
在第二种情况下,您不使用文字但x
在执行时创建新列表。因此,每次调用list
或y
都会创建一个新的列表。
答案 2 :(得分:2)
uselpa的答案是正确的。†我想扩展引用的数据,但是要进一步扩展。
如您所知,所有Scheme程序都在内部作为语法树读入。特别是在Racket中,您使用read-syntax
过程来执行此操作:
> (define stx (with-input-from-string "(foo bar)" read-syntax))
> stx
#<syntax::1 (foo bar)>
您可以使用syntax->datum
将语法树转换为基准:
> (syntax->datum stx)
'(foo bar)
quote
是一种特殊形式,它的作用是将语法树的引用部分作为基准返回。这就是为什么,对于许多Scheme实现,您的x
过程每次都返回相同的对象:它返回语法树的相同部分作为基准。 (这是一个实现细节,并且Scheme实现不需要具有此行为,但它有助于解释您看到所见内容的原因。)
正如uselpa的回答所说,如果列表非空,list
每次都会创建一个新列表。这就是为什么与list
相比,eq?
的两个单独的非空调用结果总是不同的原因。
(在Scheme中,空列表需要表示为单个对象。所以(eq? '() '())
保证为真,(eq? (list) '())
,(eq? (cdr (list 'foo)) (list))
等也是如此。 / p>
†我不会使用短语&#34;未定义的行为&#34; 比较文字,因为它很容易与UB的C和C ++含义混淆,后者是nasal demons,虽然比较文字的结果可能不是你所期望的,但它不会导致程序崩溃,等等。修改文字当然是鼻子恶魔。