没有区别:所有对象在 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
所以在这种情况下,函数调用似乎不是在复制对象,而是我从来自阅读器的两个不同对象开始。但是,始终允许实现随意复制数字,并且可以通过不同的优化设置来实现。