2

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.

Charlim
  • 521
  • 4
  • 12
  • Simplest thing to do to have mutable value is box it up, `(setf boxed_val (list val))`, then use `RPLACA` on `boxed_val`, even in a function that receives it as an argument. and of course just use `CAR` to get to the value itself. – Will Ness Jan 03 '19 at 08:51
  • https://www.xach.com/naggum/articles/3229347076995853@naggum.net.html – coredump Jan 03 '19 at 09:31

1 Answers1

5

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.

  • 1
    Note: some primitive arguments (some numbers, characters, ...) might be copied. Other arguments are not copied. the local variable is just another reference to the object then. – Rainer Joswig Jan 03 '19 at 11:58
  • @RainerJoswig: I've added a note on copying which I think is correct: feel free to edit it if it's not! –  Jan 03 '19 at 16:53
  • Alright, so I understand your point about some values being copied and others not copied vs not copied for optimization purposes. I also understand the code examples using closures to emulate reference creation. I am, however, still confused about that first point. C and CC are EQ because they are the same object. In my tests, editing C appears to have had the same affect on CC (because they are the same object). This would indicate to me (though my assumptions here may be incorrect) that the object itself is being accessed via reference by at least one of C or CC. Is this not correct? – Charlim Jan 03 '19 at 19:53
  • 1
    The `c` and `cc` bindings simply have the *same object* as their values: neither is any kind of reference, because Lisp is call-by-value. You might want to look at [this previous answer of mine](https://stackoverflow.com/a/53722227/5920214) where I talk about bindings, and also perhaps [this article](https://www.tfeb.org/fragments/2018/12/11/call-by-value-in-scheme-and-lisp/) (which is the same as the answer) & then [this article](https://www.tfeb.org/fragments/2019/01/04/function-calling-conventions-and-bindings/) which I wrote as a result of this question. –  Jan 04 '19 at 11:47
  • I thought I understood but I've gone and confused myself again. Say I have some value stored in some variable, say X. X does not contain a reference. X is passed into a function F by value, copied to Y. As such no reference is created to X. There are no references involved here whatsoever. Why, then, should "mutability" matter? Surely Y, being an isolated value within F, should be able to be modified without affecting X if there are no references at play at all. Mustn't these bindings be a form of reference a la Java objects, or C# reference-types? – Charlim Jan 05 '19 at 00:07
  • 1
    Thinking about complex structures muddles the issue. By-value means you only transfer the value of a binding, and no other information about the origin of that value. You don't get to change a binding that is not in scope. Sure, a value can be a storage for other values, in which case you have a kind of reference if the object is mutable, but that's not the point. By-value does also not imply nor contradict having a copying mechanism. – coredump Jan 06 '19 at 09:05