What's the difference between (list nil) and '(nil) in Lisp? [duplicate]
Asked Answered
S

2

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.

Spouse answered 26/4, 2015 at 23:29 Comment(6)
It looks like bar's parentheses are unbalanced.Splenic
@Splenic Corrected. The missing closing paren was just after '(nil). Sorry for this.Spouse
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.Splenic
@Splenic 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.Spouse
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.Splenic
@Splenic 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). 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.Spouse
L
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."

Lawrenson answered 27/4, 2015 at 0:10 Comment(3)
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.Spouse
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.Lawrenson
@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.Bode
C
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.

Cogency answered 27/4, 2015 at 1:0 Comment(3)
Thank you. Just to be sure, if I copy-list a quoted list, the copy is mutable, right?Spouse
@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 does.Cogency
One more question: in the CLHS page 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.Spouse

© 2022 - 2024 — McMap. All rights reserved.