Нет никакого различия: все объекты передаются по значению в Лиспе (по крайней мере, во всех Лиспах, о которых я знаю). Однако некоторые объекты изменяемые , и 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)
Если вы понимаете, как это работает, вы, вероятно, довольно много понимаете, как работают контексты и макросы в Лиспе.
Существует исключение из универсальности передачи объектов по значению в 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
Так что в этом случае похоже, что вызов функции не копирует объекты, а скорее я начал с двух разных объектов, приходящих из считывателя. Однако реализации всегда разрешено копировать числа по желанию, и это вполне может быть сделано с различными настройками оптимизации.