Charlim
Charlim

Reputation: 581

In Common Lisp, when are objects referenced and when are they directly accessed by value?

I was reading this question to try and get some insight into the answer. It specifically asks about pass-by-reference, and all of the answers seem to indicate there is no support for pass-by-reference. However, this answer would imply that, while pass by reference may not be supported, some values are indeed accessed by reference. A simpler example would involve cons cells; I can pass a cons cell to a function, and change it's cdr or car to whatever I please.

Ultimately I'd like to know if there is some clear delimination between (to use C# parlance) value-types and reference-types, and if there's any way (more convenient than the answer referenced above) to treat a value as a reference-type.

Upvotes: 2

Views: 501

Answers (1)

user5920214
user5920214

Reputation:

There is no distinction: all objects are passed by value in Lisp (at least in all Lisps I know of). However some objects are mutable, and conses are one such type. So you can pass a cons cell to a procedure and mutate it in that procedure. Thus the important consideration is whether objects are mutable or not.

In particular this (Common Lisp) function always returns T as its first value, even though its second value may not have 0 as its car or 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)

However since Common Lisp has lexical scope, first-class functions and macros you can do some trickery which makes it look a bit as if call-by-reference is happening:

(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))))

And now:

> (cbd (lambda (b)
         (setf (captured-binding-value b) 3)))
nil
3
(0 . 0)

If you understand how this works you probably understand quite a lot of how scope & macros work in Lisp.


There is an exception to the universality of passing objects by value in Common Lisp which is mentioned by Rainer in a comment below: instances of some primitive types may be copied in some circumstances for efficiency. This only ever happens for instances of specific types, and the objects for which it happens are always immutable. To deal with this case, CL provides an equality predicate, eql which does the same thing as eq, except that it knows about objects which may secretly be copied in this way and compares them properly.

So, the safe thing to do is to use eql instead of eq: since objects which may be copied are always immutable this means you will never get tripped up by this.

Here's an example where objects which you would naturally think of as identical turn out not to be. Given this definition:

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

Then in the implementation I'm using I find that:

> (compare 1.0d0 1.0d0)
nil
t

so double-precision float zero is not eq to itself, always, but it is always eql to itself. And trying something which seems like it should be the same:

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

So in this case it looks like the function call is not copying objects but rather I started off with two different objects coming from the reader. However the implementation is always allowed to copy numbers at will and it might well do so with different optimisation settings.

Upvotes: 5

Related Questions