Reference 在Common Lisp中,对象何时被引用,何时被值直接访问?

Reference 在Common Lisp中,对象何时被引用,何时被值直接访问?,reference,lisp,common-lisp,Reference,Lisp,Common Lisp,我在读书,试图对答案有所了解。它特别询问了passbyreference,所有的答案似乎都表明不支持passbyreference。然而,这意味着,虽然可能不支持通过引用传递,但某些值确实是通过引用访问的。一个简单的例子是cons单元;我可以将cons单元传递给函数,并将其更改为cdr或car,以满足我的要求 最后,我想知道(使用C#术语)值类型和引用类型之间是否有明确的区别,是否有任何方法(比上面提到的答案更方便)将值视为引用类型。没有区别:所有对象都是在Lisp中按值传递的(至少在我所知道的

我在读书,试图对答案有所了解。它特别询问了passbyreference,所有的答案似乎都表明不支持passbyreference。然而,这意味着,虽然可能不支持通过引用传递,但某些值确实是通过引用访问的。一个简单的例子是cons单元;我可以将cons单元传递给函数,并将其更改为cdr或car,以满足我的要求


最后,我想知道(使用C#术语)值类型和引用类型之间是否有明确的区别,是否有任何方法(比上面提到的答案更方便)将值视为引用类型。

没有区别:所有对象都是在Lisp中按值传递的(至少在我所知道的所有Lisp中是这样)。然而,有些对象是可变的,conse就是这样一种类型。因此,您可以将cons单元格传递给一个过程,并在该过程中对其进行变异。因此,重要的考虑因素是对象是否可变

特别是,此(公共Lisp)函数始终返回
T
作为其第一个值,即使其第二个值可能没有
0
作为其car或cdr

(defun cbv (&optional (f #'identity))
  (let ((c (cons 0 0)))
    (let ((cc c))
      (funcall f c)
      (values (eq c cc) c))))

> (cbv (lambda (c)
         (setf (car c) 1
               (cdr c) 2)))
t
(1 . 2)

但是,由于Common Lisp具有词法范围、一流函数和宏,因此您可以使用一些技巧,使其看起来有点像正在进行引用调用:

(defmacro capture-binding (var)
  ;; Construct an object which captures a binding
  `(lambda (&optional (new-val nil new-val-p))
     (when new-val-p
       (setf ,var new-val))
     ,var))

(defun captured-binding-value (cb)
  ;; value of a captured binding
  (funcall cb))

(defun (setf captured-binding-value) (new cb)
  ;; change the value of a captured binding
  (funcall cb new))

(defun cbd (&optional (f #'identity))
  (let ((c (cons 0 0)))
    (let ((cc c))
      (funcall f (capture-binding c))
      (values (eq c cc) c cc))))
现在:

> (cbd (lambda (b)
         (setf (captured-binding-value b) 3)))
nil
3
(0 . 0)
如果您了解这是如何工作的,那么您可能会了解很多范围和宏在Lisp中的工作方式


Rainer在下面的评论中提到,在Common Lisp中按值传递对象的通用性有一个例外:在某些情况下,为了提高效率,可能会复制某些基本类型的实例。这种情况只会发生在特定类型的实例上,并且发生这种情况的对象总是不可变的。为了处理这种情况,CL提供了一个相等谓词,它的作用与相同,只是它知道可能以这种方式秘密复制的对象,并正确地比较它们

因此,安全的做法是使用
eql
而不是
eq
:因为可能被复制的对象总是不可变的,这意味着您永远不会被它绊倒

这里有一个例子,你会自然而然地认为是相同的对象,结果却不是。根据这一定义:

(defun compare (a b)
  (values (eq a b)
          (eql a b)))
在我使用的实现中,我发现:

> (compare 1.0d0 1.0d0)
nil
t
所以双精度浮点零不是总是对自身而言,而是总是对自身而言。尝试一些看起来应该是一样的东西:

> (let ((x 1.0d0)) (compare x x))
t
t

因此,在本例中,函数调用似乎不是复制对象,而是从来自读取器的两个不同对象开始。然而,该实现总是允许随意复制数字,并且可能通过不同的优化设置来实现。

要获得可变值,最简单的方法是将其装箱,
(setf boxed_val(list val))
,然后在
boxed_val
上使用
RPLACA
,即使在作为参数接收的函数中也是如此。当然,只需使用
CAR
即可获得值本身。注意:一些基本参数(一些数字、字符等)可能会被复制。其他参数不会被复制。局部变量只是对对象的另一个引用。@RainerJoswig:我已经添加了一个关于复制的注释,我认为这是正确的:如果不是,请随意编辑它!好的,我理解你的观点,一些值被复制,而另一些值没有被复制,而不是为了优化目的被复制。我还了解使用闭包模拟引用创建的代码示例。然而,我仍然对第一点感到困惑。C和CC是相等的,因为它们是同一个对象。在我的测试中,编辑C似乎对CC有同样的影响(因为它们是相同的对象)。这将向我表明(尽管我在这里的假设可能不正确),对象本身正在通过引用被至少一个C或CC访问。这不正确吗?
c
cc
绑定的对象与它们的值完全相同:它们都不是任何类型的引用,因为Lisp是按值调用的。您可能想看看我在哪里谈到绑定,也可能(这与答案相同)&然后我写了这个问题的结果。思考复杂的结构会混淆这个问题。“按值”表示只传输绑定的值,而不传输有关该值的来源的其他信息。您无法更改不在范围内的绑定。当然,一个值可以是其他值的存储,在这种情况下,如果对象是可变的,那么就有一种引用,但这不是重点。按价值也不意味着也不矛盾有一个复制机制。