在Common Lisp中,何时引用对象以及何时按值直接访问对象?

时间:2019-01-03 07:31:01

标签: reference lisp common-lisp

我正在阅读this question,试图对答案有所了解。它专门询问有关通过引用的问题,所有答案似乎都表明不支持通过引用。但是,this answer表示尽管可能不支持通过引用传递,但确实确实可以通过引用来访问某些值。一个更简单的示例将涉及con单元格;我可以将一个cons单元传递给一个函数,然后将它的cdr或car更改为我想要的任何形式。

最终我想知道(使用C#说法)值类型和引用类型之间是否有明确的区分,以及是否有任何方法(比上面引用的答案更方便)将值视为引用类型。

1 个答案:

答案 0 :(得分:4)

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

特别是,此(通用Lisp)函数始终将T作为其第一个值,即使其第二个值可能没有0作为其汽车或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中范围和宏的工作原理。


在Common Lisp中按值传递对象的普遍性是个例外,Rainer在下面的评论中提到:在某些情况下可以复制某些原始类型的实例以提高效率。这只会在特定类型的实例中发生,并且发生这种情况的对象始终是不可变的。为了处理这种情况,CL提供了一个相等谓词eql,它与eq除了)具有相同的功能,因为它知道可能以这种方式秘密复制的对象并正确比较它们。

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

在此示例中,您自然会认为相同的对象却并非如此。有了这个定义:

(defun compare (a b)
  (values (eq a b)
          (eql a b)))

然后在我使用的实现中,我发现:

> (compare 1.0d0 1.0d0)
nil
t

因此,双精度浮点零始终不是自身eq,而是始终是eql。尝试看起来似乎相同的事情:

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

因此,在这种情况下,函数调用似乎不是在复制对象,而是从读取器发出的两个不同对象开始。但是,始终允许该实现随意复制数字,并且可能使用不同的优化设置也可以复制数字。