请考虑以下代码段:
(define (make)
(define b '(bottom))
b)
(eqv? (make) (make)) ; => #t
为什么会这样?我期望每次调用“make”(即放置在不同的存储器地址)时获得同一对象的不同副本,但似乎所有这些副本都是相同的(即存储在同一地址)。
为了让事情更加混乱,请考虑以下仅略有不同的代码:
(define (make)
(define b '(bottom))
(cons b b))
(eqv? (make) (make)) ; => #f
为什么现在两个副本不同(即在不同的地址)?
答案 0 :(得分:3)
quote
定义了一个文字表达式。可以将其视为编译时常量:'(bottom)
在编译程序时构建单元素列表。在整个程序执行过程中使用相同的单元素列表。每次执行(define b '(bottom))
时,它都会将b
设置为相同的值,因为它是指向同一编译时常量的指针(可以存储在只读存储区中)。因此(eqv? (make) (make))
比较两个指向同一常量的指针,它们是相等的。
在第二个示例中,(make)
的结果是调用cons
的结果。每次调用cons
都会返回一个新单元格,因此(eqv? (make) (make))
为false。
R6RS引用:11.4.1 quote
定义一个常量; 5.10常量是不可变的并且具有单个内存位置;当11.5 eqv
的两个参数位置相同时,#t
会返回cons
; 11.9 {{1}}返回一个新分配的对象。
答案 1 :(得分:1)
'(bottom)
,因为它被引用,在Scheme中是considered a constant。引用的所有内容也可以共享结构。例如:
(define one '(bottom))
(define two '(bottom))
(eq? one two) ; ==> undefined
使用undefined,您可以期望一个方案实现导致#f而另一个导致#t。即使相同的实现也可能根据编译器设置而改变。
要获得所需的行为,您需要在程序中cons
。以下是使用list
为每个调用创建唯一列表的版本:
(define (make)
(list 'bottom))
;; test
(eq? (make) (make)) ; ==> #f
(eqv? (make) (make)) ; ==> #f
(equal? (make) (make)) ; ==> #t
如果要将常量用作模板,可以使用list-copy:
(define (make)
(list-copy '(bottom)))
另请注意,常量是不可变的。双引号中的字符串也类似于引号列表。在许多计划报告中,违反报告可能不会发出错误信号,只是开始表现得很奇怪。例如
(define constant "horse")
(string-set! constant 0 #\H)
根据R5RS,上面两行不是Scheme,因为string-set!
正在改变常量。在R6RS中,它应该引发一个带有条件类型和断言的异常。但是,很少有实现。你会得到以下效果:
(define (get-constant)
"horse")
(define constant (get-constant))
(string-set! (get-constant) 0 #\H)
constant ; ==> "Horse"
(get-constant) ; ==> "Horse"
修改强>
评论你的补充:
(define (make)
(define b '(bottom))
(cons b b))
此处b
仍然是常量,但保留两者的对在每次执行时都是新的,就像我使用list
的版本一样((list 'bottom)
与{{1}相同}):
(cons 'bottom '())