首页 > 解决方案 > 在 Common Lisp 中,什么时候引用对象,什么时候直接按值访问?

问题描述

我正在阅读这个问题,试图对答案有所了解。它专门询问传递引用,所有答案似乎都表明不支持传递引用。但是,这个答案意味着,虽然可能不支持通过引用传递,但确实可以通过引用访问某些值。一个更简单的示例将涉及 cons 单元;我可以将一个 cons 单元格传递给一个函数,并将它的 cdr 或 car 更改为我喜欢的任何内容。

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

标签: referencelispcommon-lisp

解决方案


没有区别:所有对象在 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)

如果你了解它是如何工作的,你可能会了解很多 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

所以在这种情况下,看起来函数调用不是复制对象,而是我从来自阅读器的两个不同对象开始。但是,始终允许实现随意​​复制数字,并且可以通过不同的优化设置来实现。


推荐阅读