5

First of all, let me say I'm a beginner in Lisp. To be honest I have been a beginner for some time now, but there are still many things I don't know well.

While I was writing this question, I came up with a strange bug in my code.

Here is a function that will return the list (0 1 ... n) with the list e appended. It uses rplacd along the way to keep track of the last element, to avoid a final call to last.

For example, (foo 4 '(x)) returns (0 1 2 3 4 x).

The "head" is stored in a, which is not simply nil, because there is only one nil, and never a copy of it (if I understand correctly), hence I can't simply append to nil.

(defun foo (n e)
    (let* ((a (list nil)) (tail a))
        (loop for i to n
              do (rplacd tail (setf tail (list i)))
              finally (rplacd tail (setf tail e))
              (return (cdr a)))))

(defun bar (n e)
    (let* ((a '(nil)) (tail a))
        (loop for i to n
              do (rplacd tail (setf tail (list i)))
              finally (rplacd tail (setf tail e))
              (return (cdr a)))))

The only difference between these functions is the (list nil) replaced by '(nil) in bar. While foo works as expected, bar always returns nil.

My initial guess is this happens because the original cdr of a is indeed nil, and the quoted list may be considered constant. However, if I do (setf x '(nil)) (rplacd x 1) I get (nil . 1) as expected, so I must be at least partially wrong.

Community
  • 1
  • 1
  • It looks like `bar`'s parentheses are unbalanced. – user2357112 Apr 26 '15 at 23:46
  • @user2357112 Corrected. The missing closing paren was just after `'(nil)`. Sorry for this. –  Apr 26 '15 at 23:48
  • I'm not familiar with Common Lisp, but I believe in Scheme, anything quoted with `'` is supposed to be treated as immutable. Does that hold in Common Lisp? It looks like you're trying to mutate the thing. – user2357112 Apr 26 '15 at 23:50
  • @user2357112 Yes, I remember this from my past experience with Scheme. It does not *seem* to be the same in Common Lisp. But I'm cautious because I suspect my bug has something to do with this. –  Apr 26 '15 at 23:52
  • My other guess would be that in one version, you're getting a list with the symbol `nil` in it, and in the other version, `nil` is evaluated to an empty list. EDIT: No, it looks like the symbol `nil` evaluates to the symbol `nil`. – user2357112 Apr 26 '15 at 23:54
  • @user2357112 Actually, `(list nil)` and `'(nil)` represent almost the same thing. For example, they are `equal`, but not `eq` (which means they are not the same object, though they have the same "structural" contents, see [here](http://stackoverflow.com/questions/547436/whats-the-difference-between-eq-eql-equal-and-equalp-in-common-lisp)). And `nil` is a weird object, that represents both the false boolean and the empty list, and two variables set to `nil` are always `eq` (the same object), and this causes some problems when you want to initialize a list to something empty, to later update. –  Apr 26 '15 at 23:59

2 Answers2

6

When evaluated, '(nil) and (list nil) produce similar lists, but the former can be considered constant when present in source code. You should not perform any destructive operations on a constant quoted list in Common Lisp. See http://l1sp.org/cl/3.2.2.3 and http://l1sp.org/cl/quote. In particular, the latter says "The consequences are undefined if literal objects (including quoted objects) are destructively modified."

Xach
  • 11,774
  • 37
  • 38
  • This makes sense, thank you. So, to be sure, it's only thanks to *undefined consequences* if `(setf x '(nil)) (rplacd x 1)` does what I wrongly expected? Here Scheme is more friendly, since it would notify the error, IIRW. –  Apr 27 '15 at 00:16
  • When SBCL can detect the modification of constant data, it will issue a helpful warning and cite the applicable bits of the standard. (That's how I looked up the references for my reply.) But it can't detect all situations yet. Until then you just have to know the rule. – Xach Apr 27 '15 at 00:18
  • 1
    @Jean-ClaudeArbaut : An old Scheme FAQ says: Section 3.4 of R5RS states that it is an error to attempt to modify an immutable object, such as a literal list. Not all Schemes report this as an error though and instead do modify the literal list. – Rainer Joswig Apr 27 '15 at 04:14
4

Quoted data is considered a constant. If you have two functions:

(defun test (&optional (arg '(0)))
  (setf (car arg) (1+ (car arg)))
  (car arg))

(defun test2 ()
  '(0))

These are two functions both using the constant list (0) right?

  1. The implementation may choose to not mutate constants:

    (test) ; ==> Error, into the debugger we go
    
  2. The implementation can cons the same list twice (the reader might do that for it)

    (test2) ; ==> (0)
    (test)  ; ==> 1
    (test)  ; ==> 2
    (test)  ; ==> 3
    (test2) ; ==> (0)
    
  3. The implementation can see it's the same and hench save space:

    (test2) ; ==> (0)
    (test)  ; ==> 1
    (test)  ; ==> 2
    (test)  ; ==> 3
    (test2) ; ==> (3)
    

In fact. The last two behavior might happen in the same implementation dependent on the function being compiled or not.

In CLISP both functions work the same. I also see when disassembling with SBCL that the constant actually is mutated so I wonder if perhaps it has constant folded (cdr '(0)) at compile time and doesn't use the mutated list at all. It really doesn't matter since both are considered good "undefined" behavior.

The part from CLHS about this is very short

The consequences are undefined if literal objects (including quoted objects) are destructively modified.

Sylwester
  • 47,942
  • 4
  • 47
  • 79
  • Thank you. Just to be sure, if I **copy-list** a quoted list, the copy is mutable, right? –  Apr 27 '15 at 08:41
  • 1
    @Jean-ClaudeArbaut Yes, but only the top level since `copy-list` makes a shallow copy. Thus it does not make copies recursively of list elements in the list like [`copy-tree`](http://www.lispworks.com/documentation/lw61/CLHS/Body/f_cp_tre.htm#copy-tree) does. – Sylwester Apr 27 '15 at 10:26
  • One more question: in the [CLHS page](http://www.lispworks.com/documentation/HyperSpec/Body/f_nconc.htm) for function **nconc**, there is an example: *(setq x '(a b c)) (setq y '(d e f)) (nconc x y)* Isn't this wrong? I mean, both x and y hold a litteral list, and x is modified by nconc. –  Apr 30 '15 at 09:05