| > First, the list terminator need not be the same thing as the empty list. No it doesn't, but that choice happens to give us a compact recursive definition. > There can be multiple list terminators ... multiple empty lists ... Sure, and 2022 can be written MMXXII, and whatnot. Mathematically, there is one empty list, so why proliferate it? There can be multiple empty strings which is useful if strings are mutable. If strings are immutable, it's silly to have more than one empty string. The empty list is immutable, so ... > under no circumstances should taking the CAR or CDR of an empty list do anything other than signal an error. Lisp 1 and 1.5 had it that way, certainly. It's mostly just inconvenient. A good mix is to have strict vectors and strings which signal on out-of-bounds, but lists which are forgiving. Forgiving lists allow good old Ashwin Ram to have: (cdr (assq key a-list))
rather than (let ((val (assq key a-list)))
(cond ((not (null? val)) (cdr val))
(else nil)))
|
I guess Common Lisp is silly then.
> (cdr (assq key a-list))Much better to have an abstract associative map (dictionary) type with an opaque implementation rather than punning cons cells (which locks you in to an O(n) implementation). ALists are interesting from a historical point of view but they should never be used in modern code without hiding them under a layer of abstraction.
And even if you are going to pun cons cells to build an associative map, alists are the wrong way to do it because it forces you to duplicate the keys for every frame. Much better to use D-lists ((key1 key2 ...) val1 val2 ...) because that lets you re-use the key list, which can cut your memory usage in half, and provides a much more straightforward path from interpreter to compiler for pedagogical purposes.