首页 > 解决方案 > 在 Common Lisp 中传递对象引用

问题描述

Peter Seibel 在 Practical Common Lisp 中写道:“Common Lisp 中的所有值至少在概念上都是对对象的引用。”

我用以下代码尝试了这个概念:

(setf x 5)
(setf y x)
(print x) % output: x is 5
(print y) % output: y is 5
(setf x 6)
(print x) % output: x is 6
(print y) % output: y is 5

如果 Lisp 是按对象引用传递的,则 y 应该指向 x,因此将 x 更改为 6 也应该将 y 更改为 6。但事实并非如此。看起来 Lisp 是按对象值传递的。有人可以帮忙解释发生了什么吗?

标签: lispcommon-lisppass-by-referencepass-by-value

解决方案


在 Lisp 实现中,小整数通常不被引用。说所有值都是 Common Lisp 中的引用是不正确的。在怀疑值是引用的情况下进行编程通常是更安全的假设,更符合编写正确的代码。

但是,您的示例与作为引用实现的小整数一致。这并不能证明他们不是。

如果一个变量x持有一个像 一样的整数5,然后我们分配给xwith (setf x 4),我们并没有将对象变异54。我们正在改变变量绑定x:我们已经用新值覆盖了5之前存在的值。x4

即使我们使用正向引用到堆中的对象(如 cons 单元格),这也将起作用:

(setf x '(1 . 2))
(setf y x)
y -> (1 . 2)
(setf x '(4 . 5))
y -> (1 . 2)

xy是自变量,并且独立地持有对 cons 单元格的引用。x最初持有对该(1 . 2)对象的引用。我们分配xy,所以现在y也持有对 的引用(1 . 2)。它有自己的参考副本。因此,当我们分配 时(4 . 5)x的引用被对 的引用覆盖(4 . 5),但y不受影响。为什么应该这样?

我们如何证明 conses 使用引用语义是通过改变单元格本身:

(setf x (cons 1 2)) ;; we better not use a literal any more!
x -> (1 . 2)
(setf y x)
y -> (1 . 2)

;; now mutate

(rplaca x 10)
(rplacd x 20)

x -> (10 . 20)
y -> (10 . 20)

由于对存储在中的单元格x进行突变会使突变出现在存储在中的单元格上y,因此我们知道x并且y必须持有对同一对象的引用。

现在问题来了:我们不能用整数执行这个测试,因为整数是不可变的!没有类似的函数rplaca会破坏表示的实际位1,并将它们转换为10.

eq函数没有帮助,因为它所能做的就是确认这两个值是同一个对象:

(setf x 5)
(setf y x)

(eq x y) -> ?

如果此eq调用返回T,则xy是同一个对象。我说如果因为 ANSI Common Lisp 离开了这个实现特定的。允许实现屈服NIL

但是,它T在小整数(fixnums)被直接打包到一个值中的实现中产生,而不是指向装箱堆对象的指针(流行的实现方法)。也就是说,一个像这样的值4在它出现的任何地方都被认为是一个对象,即使它是通过复制一个保存4直接表示的位模式的值来传播的。

所以它或多或少归结为你只需要知道你的实现是如何工作的,以确定哪些类型的对象是引用。


推荐阅读