3

I'm using this Lisp compiler for testing

There is one things that I don't get it : If an empty list () evaluate to itself :

(format t "~:a" ())
;; => ()

why do evaluating several nested empty list () don't evaluate to an empty list ?

(format t "~:a" (()))
;; => EVAL: undefined function NIL
;; why not () ?

(format t "~:a" ((())))
;; => EVAL: (NIL) is not a function name; try using a symbol instead
;; why not () ?

From my point of view, () and NIL are the same, so the inner empty list should first evaluate to itself, then the outer list should "call" the newly inner evaluated empty list

I my head, I see it like this should happen when doing the calculation: (bold + italic = what is currently evaluated)

(()) => (()) => (()) => ()

Seem like Scheme also don't allow nested empty list call

Thank for reading

Thomas
  • 75
  • 1
  • 9
  • In your diagram the last arrow is where things are different from your expectations: `(x)` for some value `x` doesn't return `x` itself, it is a function call – coredump Aug 10 '23 at 21:34

1 Answers1

9

In Common Lisp and its ancestors, the empty list, (), is self evaluating by special dispensation: it's the only list which has this property. It's also the only list which is also a symbol, in this case the symbol nil (really NIL). It's tempting to think that, somewhere in the guts of the implementation something just said the equivalent of (defconstant nil ()), but it's much more magic than that:

> (eq () 'nil)
t

> (symbolp ())
t

> (symbol-name ())
"NIL"

It's not just the value of the symbol nil, it is the symbol nil. () is really very magic in CL. More magic than it is in Scheme, say: in Scheme it's still magic just rather less so. In Scheme, () is the only list which is not a pair (a cons), but it is not also a symbol and it is not self-evaluating.

So, OK, that explains why () evaluates to itself: it does so because it is magic.

So now consider trying to evaluate this form:

(())

Well, first of all this is not the empty list: its a list with one element, which is the empty list. From the point of view of evaluation it is a compound form. Well, as the empty list (the first element of this list) is also nil we can rewrite this compound form as

(nil)

This is the same thing: (equal '(nil) '(())) is true.

OK, well now what does the evaluator do with compound forms? It looks at their first element and decides what to do (see here.

  • If it is a symbol, then the symbol should name one of

    • a special operator
    • or a (possibly local) function
    • or a (possibly local) macro

    and it will be processed accordingly.

  • If it is not a symbol it may be a list beginning (lambda ...) which is turned into an anonymous function and called.

  • There are no more cases.

Well, which is (()), or equivalently (nil)? It's a compound form, and the first element of it is the symbol nil. So

  • does nil name a special operator? no -- (special-operator-p 'nil) is false;
  • does nil name a macro? no -- (macro-function 'nil) is false;
  • does nil name a function? No - (fboundp 'nil) is false.

So the evaluator can't process this form: it's not legal.


Note that in Scheme the same thing would also be not legal, but for slightly different reasons: for Scheme, the empty list is not self-evaluating, so the evaluator sees (()) and says (ignoring macros this time) that this is a procedure call, where the procedure is going to be the result of evaluating (), but that's an error. If I said (define nil '()) then (nil) would still be an error: it would evaluate nil and get (), but () is not a procedure.


A surprising thing about CL is that (()) can be given (local) meaning. This is, amazingly enough, conforming CL:

(flet ((() () ()))
  (()))

It's conforming because you're allowed to locally establish function definitions for symbols in CL if they do not have global function definitions:

If an external symbol of the COMMON-LISP package is not defined as a standardized function, macro, or special operator, it is allowed to lexically bind it as a function (e.g., with flet), to declare the ftype of that binding, and (in implementations which provide the ability to do so) to trace that binding. -- CLHS

And then () is the symbol nil which does not have a global function definition.

ignis volens
  • 7,040
  • 2
  • 12
  • Oh okay I see thank, it because of compound and because the list is not empty, it's containing one element, the empty list. (Fun fact : I was making a Lisp/Scheme interpreter in C, and they way I implemented it `(())` work but I was wondering about why it don't in lisp/scheme). Thank for the links too ! – Thomas Aug 10 '23 at 10:17
  • 2
    I really like the bit about: "(()) can be given (local) meaning." My first thought was "but you're binding a symbol in CL..." and then you provided the exact reference that says "you can do that, if it doesn't already have a binding (of certain types)". That attention to detail is great! – Joshua Taylor Aug 10 '23 at 12:10
  • 1
    `()` isn't magic; it's just the empty list, one element shorter than `(42)`. What's magic is that the empty list is the symbol `nil`. So when the reader scans `()`, it has to return `nil`. Since `nil` is self-evaluating, `()` has to be also. If `nil` were not self-evaluating, but had to be quoted, so would `()`. In early Lisp, `T` was not self-evaluating and had to be quoted; then they bound a global variable called `T` which had itself as a value. A similar thing could have been done with `NIL`; and that would not be magic at all, just `()` producing `NIL`, which is then resolved as a var. – Kaz Aug 10 '23 at 15:58
  • 1
    @Kaz It is magic (or I consider it so). In both Scheme and CL it is a list but not a cons, which is entirely more magic than being (say) just a zero-length vector. In CL it is additionally magic because `car` & `cdr` work on it even though it is not a cons and finally that it is a list *and a symbol* which is quite seriously magic. That's how I define 'magic'. – ignis volens Aug 12 '23 at 15:46