公共Lisp中的按对象引用传递

公共Lisp中的按对象引用传递,lisp,common-lisp,pass-by-reference,pass-by-value,Lisp,Common Lisp,Pass By Reference,Pass By Value,Peter Seibel在实用的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

Peter Seibel在实用的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是通过对象值传递的。有人能解释一下发生了什么吗?

在Lisp实现中,小整数通常不是引用。说所有值都是公共Lisp中的引用是不正确的。在怀疑值是引用的情况下编程通常是更安全的假设,与编写正确的代码更为一致

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

如果变量
x
包含一个类似
5
的整数,然后我们用
(setf x 4)
赋值给
x
,我们就不会将对象
5
变为
4
。我们正在变异变量绑定
x
:我们已经用新值
4
覆盖了先前在
x
中的
5

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

(setf x '(1 . 2))
(setf y x)
y -> (1 . 2)
(setf x '(4 . 5))
y -> (1 . 2)
x
y
是自变量,独立地保存对cons单元格的引用
x
最初保存对
(1.2)
对象的引用。我们将
x
分配给
y
,因此现在
y
也包含对
(1.2)
的引用。它有自己的参考副本。因此,当我们分配
(4.5)
时,
x
的引用被
(4.5)
的引用覆盖,但
y
不受影响。为什么会这样

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

(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
,则
x
y
是同一对象。我说如果是因为ANSI Common Lisp将此实现保留为特定的。允许实现产生
NIL

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

因此,或多或少可以归结为,您只需知道您的实现是如何工作的,以确定哪些类型的对象是引用。

正如@Kaz所提到的, 字符串、数字、符号等文字是以一种特殊的方式实现的,因此在分配给它们时,它们大多会被复制/重新创建

但是一旦你生成了一些更复杂的结构——组合结构-
setf
将引用该位置

(defparameter *x* (list 3))
(setf *y* *x*)
*x* ;; (3)
*y* ;; (3)

;; mutate *x*
(setf (car *x*) 5)
*x* ;; (5)
*y* ;; (5)

;; if you mutate *x* by letting things be there and adding sth e.g.
(setf (cdr *x*) 7)
*x* ;; (5 . 7)
*y* ;; (5 . 7)

(setf (cdr *x*) (list 1 2))
*x* ;; (5 1 2)
*y* ;; (5 1 2)

;; however, completely refedine *x*
;; then *y* will still refer to the old *x*
;; maybe because the creation of a new object (list 5 6) 
;; makes the symbol *x* be bound to a completely new place 
;; - where (list 5 6) was generated.
(setf *x* (list 5 6))
*x* ;; (5 6)
*y* ;; (5 1 2)

;; at least we can say, as long as we are mutation on the structures
;; of the cons cells - *y* will follow *x*'s mutations

您想要做的事情,可以通过使用函数(使用闭包)引用变量来实现 从现在起,无论您使用
(setf*q*)
*q*
指定什么值, 您将能够通过
(y)

但是,要使用
(setf(y)
更改
*q*
,您必须首先定义相应的
setf
函数(并以修改的方式定义它,然后直接修改
*q*

要有更多的感觉调用变量而不是函数, 人们可能会创建一个readermacro,例如为
@y
创建一个readermacro,然后扩展到
(y)
。以及定义宏(并在后台定义相应的
setf
函数…),这样人们至少会有处理变量的错觉


这些只是我的想法。

[这个答案大部分重复了其他人的回答:我在他们存在之前就开始了,不想扔掉它。]

Lisp(特别是普通的Lisp,但其他的也一样)是严格按值调用的

(let ((x ...))
  (let ((y x))
    (f x)
    (eql y x)))
这是真的

然而,许多值都是引用,或者换句话说,许多类型的对象都具有标识

从实现上来说,这通常意味着大多数值都是内存中对象的地址

(let ((x (cons nil nil)))
  (let ((y x))
    ...))
然后,
x
的值实际上是由
cons
构造的对象的地址,
y
的值也是如此。这意味着,例如

(defun mutate-cdr (c to)
  (setf (cdr c) to))
然后

3

然而,在一些特殊情况下,某些类型的对象会立即被表示:通常是小数字、字符或其他一些东西。所有立即被表示的对象都是不可变的,因此不会有太大的区别,尽管与此相关的等式有复杂的规则

但是,Lisp功能强大,如果您需要,可以支持引用调用,只需使用一点语法:

(declaim (inline locf (setf locf)))

(defun locf (loc)
  (funcall loc 'get))

(defun (setf locf) (new loc)
  (funcall loc 'set new))

(defmacro locative (place)
  `(lambda (op &optional val)
     (ecase op
       ((get) ,place)
       ((set) (setf ,place val)))))
现在给

(defun mutate-loc (loc new)
  (setf (locf loc) new))
然后

4
。当然,这适用于任何地方:

(let ((x (vector 1 2 3)))
  (let ((y (locative (aref x 2))))
    (mutate-loc y 4)
    x))
是第二个元素为
4
的向量

有些实现将类似这样的东西内置到语言中:我不知道
(defun mutate-loc (loc new)
  (setf (locf loc) new))
(let ((x 1))
  (let ((y (locative x)))
    (mutate-loc y 4)
    x))
(let ((x (vector 1 2 3)))
  (let ((y (locative (aref x 2))))
    (mutate-loc y 4)
    x))
(defun display-and-update (text)
  (print text)
  (setf text (concatenate 'string text ", updated.")))
(let ((my-text "test"))
  (display-and-update my-text)
  (print my-text))
> "test"
> "test"
(defun display-and-update (sym-text)
  (print (symbol-value sym-text))
  (set sym-text (concatenate 'string
                             (symbol-value sym-text)
                             ", updated.")))
(let ((my-text "test"))
  (declare (special my-text))
  (display-and-update 'my-text)
  (print my-text))
> "test"
> "test, updated."
int a = 3;
int *b = &a;
*b = 5; // a is 5
(defparameter a 3)
(defparameter b 'a)
(set b 5) ;; a is 5
(defparameter a 3)
(defparameter b nil)
;; (set b 'a) ;; this won't work, as it's trying to set the symbol nil's value to 'a
;; instead:
(setf b 'a) ;; this is a better match for C's = operator