在Common Lisp中,对象什么时候被引用,什么时候被值直接访问



我读这个问题是为了深入了解答案。它特别询问了通过引用,所有的答案似乎都表明不支持通过引用。然而,这个答案意味着,虽然可能不支持通过引用传递,但某些值确实是通过引用访问的。一个更简单的例子涉及cons-cell;我可以将一个cons单元格传递给一个函数,并将其更改为cdr或car。

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

没有区别:所有对象都是通过Lisp中的值传递的(至少在我所知道的所有Lisp中是这样)然而有些对象是可变的,conses就是这样一种类型。因此,你可以将一个cons细胞传递到一个程序中,并在该程序中对其进行突变。因此,重要的考虑因素是对象是否是可变的。

特别地,这个(Common 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)

如果你了解这是如何工作的,你可能会了解很多scope&宏在Lisp中工作。


Rainer在下面的评论中提到了Common Lisp中按值传递对象的通用性的一个例外:为了提高效率,某些基元类型的实例可能在某些情况下被复制。这种情况只发生在特定类型的实例中,并且发生这种情况的对象总是不可变的。为了处理这种情况,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

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

最新更新