Search code examples
lispcommon-lispsbcl

Why does (nil . nil) evaluate to (nil) instead of just nil in SBCL?


In the SBCL REPL, why does entering '(nil . nil) evaluate to (nil) and not just nil?

If an empty list is one where both "elements" of the cons cell are nil, why are these not the same?

My assumption for this is that SBCL makes the following evaluations:

(car '()) => nil
(cdr '()) => nil
(car '(nil . nil)) => nil
(cdr '(nil . nil)) => nil

And yet:

'() => nil
'(nil . nil) => (nil)

Solution

  • If an empty list is one where both "elements" of the cons cell are nil,

    That is not the case. The empty list, (), is just a different way to spell NIL, and NIL does not have a CONS cell, rather it is a symbol, similar to EAT and X.

    Common Lisp the Language, 2nd Edition, 6.1. Logical Values:

    The names nil and t are constants in Common Lisp. Although they are symbols like any other symbols, and appear to be treated as variables when evaluated, it is not permitted to modify their values. See defconstant.

    [Constant] nil

    The value of nil is always nil. This object represents the logical false value and also the empty list. It can also be written ().

    And, from Practical Common Lisp, 4. Syntax and Semantics:

    This equivalence between NIL and the empty list is built into the reader: if the reader sees (), it reads it as the symbol NIL.

    Therefore, the empty list is not a list at all! Now, about this:

    (car '()) => nil
    (cdr '()) => nil
    

    Because the empty list is not a list, it doesn't have a CONS cell, yet CAR and CDR are used to peer into a CONS cell--except in one special case: CAR and CDR are defined to return NIL for an empty list. The result of that special case is that you can't use CAR and CDR to distinguish between the empty list, (), and the list (nil):

    * (defvar *x* (list nil))
    *X*
    * *x*
    (NIL)
    * (car *x*)
    NIL
    * (cdr *x*)
    NIL
    
    * (setf *x* NIL)
    NIL
    * *x*
    NIL
    * (car *x*)
    NIL
    * (cdr *x*)
    NIL
    

    Because the empty list, (), is just another way to spell NIL, the previous paragraph could be rewritten as:

    Because NIL is not a list, it doesn't have a CONS cell, yet CAR and CDR are used to peer into a CONS cell--except in one special case: CAR and CDR are defined to return NIL for NIL. That means you can't use CAR and CDR to distinguish between NIL and the list (nil).

    ======

    Dotted Lists

    A dotted list is one whose last cons does not have nil for its cdr, rather some other data object...Such a list is called "dotted" because of the special notation used for it: the elements of the list are written between parentheses as before, but after the last element and before the right parenthesis are written a dot (surrounded by blank space) and then the cdr of the last cons. As a special case, a single cons is notated by writing the car and the cdr between parentheses and separated by a space-surrounded dot. For example:

    (a . 4)         ;A cons whose car is a symbol 
                    ; and whose cdr is an integer 
    (a b c . d)     ;A dotted list with three elements whose last cons 
                    ; has the symbol d in its cdr
    

    A list containing the single integer 9 looks like this:

          CONS cell
    +---------+--------+ 
    |  CAR    |  CDR   | 
    |   *     |   *----+---> nil
    +---|-----+--------+
        |
        V
        9 
    

    In list syntax, that is written as:

    (9)
    

    In dotted list notation, that is written as:

    (9 . nil)
    

    Dotted list notation explicitly states both the CAR and the CDR of an element in the list. With the list syntax, a CDR pointing to NIL is implicit for the last element of the list.

    Compare (nil . nil) to (9 . nil): (9 . nil) is a list with one element 9, which similarly means that (nil . nil) must be a list with one element nil:

      |
      V
    ( 9  . nil)  --> (9)
    (nil . nil)  --> (nil)
      ^
      |
    

    Drawing the CONS cell for (nil . nil):

          CONS cell
    +---------+--------+ 
    |  CAR    |  CDR   | 
    |   *     |   *----+---> nil
    +---|-----+--------+
        |
        V
       nil 
    

    Compare that to the drawing of the CONS cell for the list (9) above. In both cases, list syntax puts parens around the CAR.