Why nil cannot be matched in case/ecase?

Viewed 255

The following form

(let ((foo nil))
  (ecase foo
    (:bar 1)
    (:baz 2)
    (nil 3)))

throws error NIL fell through ECASE expression. The workaround for this seems to be to wrap nil case with parenthesis, like this:

(let ((foo nil))
  (ecase foo
    (:bar 1)
    (:baz 2)
    ((nil) 3))) ;; => 3

but why the first example doesn't work? Does unwrapped nil case have some special meaning?

1 Answers

Each clause in case can be match a single item or a list of items, e.g. you can write:

(ecase foo
  (:bar 1) ;; match a specific symbol
  ((:baz :quux) 2)) ;; match either of these symbols

NIL is also the empty list in Lisp, and when it's used as the test in case this is how it's treated, so it never matches anything. Similarly, T and OTHERWISE are used to specify the default case, so you can't match them as single items. To match any of these, you need to put them in a list.

More technically, the specification says this about the keys in a clause:

keys---a designator for a list of objects. In the case of case, the symbols t and otherwise may not be used as the keys designator. To refer to these symbols by themselves as keys, the designators (t) and (otherwise), respectively, must be used instead.

and the definition of a list designator says:

a designator for a list of objects; that is, an object that denotes a list and that is one of: a non-nil atom (denoting a singleton list whose element is that non-nil atom) or a proper list (denoting itself).

Notice that the case where a single atom is treated as a singleton list says "non-nil atom". That permits NIL to fall into the second case, where a proper list denotes itself. Otherwise, there would be no way to create a list designator for an empty list, since NIL would denote (NIL).

You might argue that there's not much point in having an empty list of keys in CASE, since it will never match anything and the whole clause could just be omitted. This degenerate case is there for the benefit of automated code generation (e.g. other macros that expand into CASE), since they might produce empty lists, and these should be treated consistently with other list. It would be harder for them to make the generation of the clause conditional on whether there are any keys.

Related