Earlier quoted context omitted.
The name "dotted list" comes from how the list is serialized, yes, but not the concept. You can write a function dotted-list-p that takes an object and returns the value of (and (consp object) (cdr (last object))). Indeed, it is not related to the NIL issue, and I made no such assumption. You can read an equivalent definition that uses different words in the CLHS glossary. You admit to talking about "how NIL is imple…
> having CAR and CDR take lists instead of conses. (car nil) and (cdr nil) safely returning nil was introduced in InterLisp, according to Gabriel's HOPL palper. At some big Lisp summit in the early 1970's, InterLisp decided to adopt MacLisp's readtables, and MacLisp adopted (car nil) -> nil. Why the empty list is a symbol is natural: math uses symbols to refer to such things. For instance the empty set is notated bot…
The empty list
91–100 of 109 posts
Re: The empty list
#92Earlier quoted context omitted.
The name "dotted list" comes from how the list is serialized, yes, but not the concept. You can write a function dotted-list-p that takes an object and returns the value of (and (consp object) (cdr (last object))). Indeed, it is not related to the NIL issue, and I made no such assumption. You can read an equivalent definition that uses different words in the CLHS glossary. You admit to talking about "how NIL is imple…
> The name "dotted list" comes from how the list is serialized, yes, but not the concept. Yes, it does. Dotted lists are entirely related to serialization. The only reason they matter is that, by convention, (a b ... z) is a shorthand notation for (a . (b . (... (z . nil)) ...) and (a b ... z . anything-but-nil) is a shorthand notation for (a . (b . (... (z . anything-but-nil)) ...) > You can write a function dotted-…
Of course NIL is an ordinary symbol. It has roles, the same way other symbols have roles. Are you saying the symbol &BODY is not an ordinary symbol because it is a lambda list keyword? Are you saying the symbol T is not an ordinary symbol because type boolean is (member t nil)? Are you saying symbol * is not an ordinary symbol because it is a special variable bound by the REPL, it is used in declaration syntax, and also is the name of the product function? Are you saying MUMBLE:VAPORIZE is not an ordinary symbol because it names the most dangerous operation in the mumble library? They are all ordinary symbols, sometimes with unique roles. Because NIL was chosen to satisfy the role of the empty list, is it any wonder that type list is (or cons null), type null is (eql nil), LENGTH and ASSOC and other list functions can deal with it etc.? Of course not. That doesn't make NIL qua symbol any weirder than any other symbol that has particular roles in a given context.
There are many symbols in Common Lisp that you cannot DEFUN. See section 11.1.2.1.2 in the hyperspec. Since NIL names a constant variable but not a standardized function, macro, or special operator, you can bind it to a function lexically using FLET or LABELS.
Once again, NIL is not a cons, it is an atomic symbol that also represents the empty list, so it's no wonder that LISTP returns true and CONSP returns false. CAR and CDR take lists (again see their entries in the CLHS) so it's no wonder that they can work with it. RPLACA and RPLACD take conses only so it's no wonder that they don't. At the risk of stating the obvious (again and again) the type null is (eql nil) so it's no wonder that NULL returns T for it. That operators treat a symbol specially does not make a symbol weird. It merely means that it serves a particular role. Any symbol can take any number of roles within a program. That doesn't make the symbol weird. I will repeat once more, because I've a feeling you didn't get this. It doesn't make a symbol weird.
It is a basic matter, definitely. I learned all this stuff about conses, atoms, symbols, lists, etc. very early on, as baby Lisper. I don't know why someone who spent 35 years programming Lisp should have trouble understanding all this, and has to keep bringing up new irrelevant/incorrect claims instead of just opening a beginner's Lisp book and re-reading the first chapter or two, where they talk about all this stuff.
Re: The empty list
#93Earlier quoted context omitted.
When baby Lispers are born, the first thing they are taught is a dichotomy: the universe is split into conses and atoms. Cons cells are simple and composed of two components, which are called the car and cdr. There are functions to retrieve what's stored in these components, which are called CAR and CDR. Atoms may be as complex as you like. They include objects like numbers, characters, strings, arrays, symbols, etc.…
(a b . c), as an object is an improper list. The printed notation is a dotted list. By an informal metonymy, the internal object is called a dotted list. It's only an informal usage among Lisp coders. The correct terminology is "improper" for the object and "dotted" for the spelling. Note that (a b c . nil) is dotted, but it's the same as (a b c) which is proper. Unfortunately, the Common Lisp specification encodes t…
Re: The empty list
#94Earlier quoted context omitted.
`append` on lists is equivalent to concatenation, not Python's append which is equivalent to `vector-push` or `vector-push-extend` in Lisp (and which, as the name suggests, takes a vector for the place and not a list). For performance reasons, the more conventional behavior in Lisp to collect values would be: (let ((some-place nil)) (push an-item some-place) ... (nreverse some-place)) ;; if the order really matters,…
> For performance reasons, the more conventional behavior in Lisp to collect values would be What you describe is what a human typical would write. For tools this is optimized. For example in (loop for e in list collect (oddp e)) this usually would be optimized. It would not add to the end and then reverse. Instead, it would keep a pointer to the end and add to the end via that pointer.
(let* ((list (list 1 2 3 4))
(last (last list)))
(rplacd last (list 5 6))
(setf last (last last))
(format t "list: ~a~%last: ~a~%" list last))
=> list: (1 2 3 4 5 6)last: (6)
Re: The empty list
#95Earlier quoted context omitted.
> The name "dotted list" comes from how the list is serialized, yes, but not the concept. Yes, it does. Dotted lists are entirely related to serialization. The only reason they matter is that, by convention, (a b ... z) is a shorthand notation for (a . (b . (... (z . nil)) ...) and (a b ... z . anything-but-nil) is a shorthand notation for (a . (b . (... (z . anything-but-nil)) ...) > You can write a function dotted-…
The point was that dotted-list-p doesn't have anything to do with the "serialized form" (printed representation) of the list. Dotted lists are important because they are one of the two types of improper list, the other type being circular lists. Many Lisp functions work on proper lists. Of course NIL is an ordinary symbol. It has roles, the same way other symbols have roles. Are you saying the symbol &BODY is not an…
It does: the details of the serialization are the reason that the concept of "dotted list" even exists. At the risk of belaboring the obvious, dotted lists are called "dotted lists" because there is a syntactically-significant dot in their serialization.
> There are many symbols in Common Lisp that you cannot DEFUN. See section 11.1.2.1.2 in the hyperspec.
It is not that you cannot defun them, it is that this is undefined behavior. At least one implementation (CCL) lets you defun just about everything except NIL:
? (defun &body () t)
&BODY
? (&body)
T
? (defun t () nil)
T
? (t)
NIL
But, as noted earlier... ? (defun nil () t)
> Error: Using NIL as a function name is silly.
> Since NIL names a constant variable but not a standardized function, macro, or special operator, you can bind it to a function lexically using FLET or LABELS.NIL's lack of weirdness in this one regard actually seems pretty weird to me. IMHO it would actually makes sense to prohibit defining a function whose name is the same object that designates an empty list, just as it makes sense to prohibit defining functions whose names are (say) numbers.
> That operators treat a symbol specially does not make a symbol weird.
That all turns on how you define "weird". One dictionary definition of "weird" is "strikingly odd or unusual, especially in an unsettling way; strange". The fact that the CAR and CDR of NIL are both NIL seems weird to me. The fact that NIL is the canonical boolean false is NIL rather than F seems weird to me. (Actually, the canonical booleans, if they were going to be symbols at all, should have been :T and :F or :TRUE and :FALSE. But that's a different discussion.)
> Are you saying the symbol &BODY is not an ordinary symbol because it is a lambda list keyword?
Yes. Lambda-list keywords are weird. They behave differently from X and Y and Z and BAZ and BAR and BING and BIFF and BOFF and ORDINARY-SYMBOL and even WEIRD-SYMBOL. The same is true for symbols interned in the keyword package. All of these things are weird.
Re: The empty list
#96Earlier quoted context omitted.
`append` on lists is equivalent to concatenation, not Python's append which is equivalent to `vector-push` or `vector-push-extend` in Lisp (and which, as the name suggests, takes a vector for the place and not a list). For performance reasons, the more conventional behavior in Lisp to collect values would be: (let ((some-place nil)) (push an-item some-place) ... (nreverse some-place)) ;; if the order really matters,…
> For performance reasons, the more conventional behavior in Lisp to collect values would be What you describe is what a human typical would write. For tools this is optimized. For example in (loop for e in list collect (oddp e)) this usually would be optimized. It would not add to the end and then reverse. Instead, it would keep a pointer to the end and add to the end via that pointer.
Re: The empty list
#97Earlier quoted context omitted.
To help someone learn to use a language, we should be judgemental and call special attention to all of its shortcomings and gotchas. I don't see value in sweeping them under the rug. That kind of language advocacy is a zero-sum game (or negative-sum). For example: > ... it is not at all strange that there is an object whose type is both list and symbol. Actually, it is strange, and unfortunate. One should think about…
Lisp dialects that have deviated from these choices require the programmer to write hideously verbose code, unable to take advantage of the economic idioms they afford.
Re: The empty list
#98Earlier quoted context omitted.
Lisp dialects that have deviated from these choices require the programmer to write hideously verbose code, unable to take advantage of the economic idioms they afford.
What useful idiom is afforded by nil being a symbol?
One is that, in the role of the list terminator, nil effectively serves as a symbol. It is unique, and tested for its identity: if the cdr of the cell of a list is the nil object, then that is the last cell. This is how symbols are used. Anything that is exploited for its unique identity should be a symbol.
Nil isn't necessarily implemented the same way as other symbols. It could actually be a null pointer. One way to look at it is that something now known as the GoF Null Object Pattern is at work. All the functions which take a symbolic argument, such as symbol-name, symbol-value, symbol-package, ... are made to work with a nil argument. Just like (car nil) is made work with a nil argument, even though nil isn't a cons.
Being able to pass nil to symbol functions makes some things work that otherwise wouldn't, which can reduce the code. All the special case testing (if necessary) is done in the library already.
Say we have a hash table H which maps keys to symbols.
We can do, say, (symbol-value (gethash K H)). If K is not found in the table, gethash will return nil. Because nil is a symbol, (symbol-value nil) works fine and returns nil. If that's what we want in the not found case, it keeps our code short and sweet.
Whenever some slot or variable defaults to nil, it's not only defaulting to a false value, and empty list, but to a valid symbol object that can be passed to symbol API's.
If the empty list weren't a symbol, it could be notated (), and nothing but that. That notation works grewat when we are dealing with the empty list semantics. It's not so nice when () is denoting false, other than () kind of looking like a zero. When () denotes the bottom type in the type system, things are also not nice; all other types are either symbols or expressions like (or integer string). It's much nicer to have a word like "nil" for all these roles.
Re: The empty list
#99Earlier quoted context omitted.
> having CAR and CDR take lists instead of conses. (car nil) and (cdr nil) safely returning nil was introduced in InterLisp, according to Gabriel's HOPL palper. At some big Lisp summit in the early 1970's, InterLisp decided to adopt MacLisp's readtables, and MacLisp adopted (car nil) -> nil. Why the empty list is a symbol is natural: math uses symbols to refer to such things. For instance the empty set is notated bot…
Having a symbol DENOTE the empty list and having a symbol be THE SAME OBJECT as the empty list are two very different things. The former is perfectly reasonable. The latter causes no end of difficulties.
It brings in its own difficulties. If nil is a variable/constant which evaluates to some nil object, then to talk about nil itself, we have to quote it.
The object which it denotes doesn't print as nil; it has its own printed rep like () and we will end up seeing that printed rep and using it. So then we have two nil representations to deal with: the nil variable which we can use in evaluated contexts, and the literal nil like () that we use elsewhere.
If () isn't self-evaluating (like the criminally stupid design in Scheme), we have to quote it: '().
If we have additional semantic roles for () like it being Boolean false and the bottom of the type spindle, those uses are not nicely served by the () notation, from an esthetic point of view.
The list terminator is de facto a symbol because it's exploited for its identity: we care whether the cdr of a cons is or is not nil, and that's all. That's a symbolic behavior; we might as well complete things so that the symbol functions work with nil; it can have a property list, name and so on.
Re: The empty list
#100Earlier quoted context omitted.
Having a symbol DENOTE the empty list and having a symbol be THE SAME OBJECT as the empty list are two very different things. The former is perfectly reasonable. The latter causes no end of difficulties.
The former is somewhat reasonable for having pi denote 3.14159... and such. It brings in its own difficulties. If nil is a variable/constant which evaluates to some nil object, then to talk about nil itself, we have to quote it. The object which it denotes doesn't print as nil; it has its own printed rep like () and we will end up seeing that printed rep and using it. So then we have two nil representations to deal w…
So? That's no different than if you want to talk about 'pi rather than pi a.k.a. 3.14159...
> So then we have two nil representations to deal with
No different than "pi" and "3.14159".
> If () isn't self-evaluating (like the criminally stupid design in Scheme), we have to quote it: '().
I agree, () should be self-evaluating just like numbers and vectors. The behavior of () should be analogous to the behavior of 0 or 0.0 or #() or "".
> If we have additional semantic roles for () like it being Boolean false
And why would you want to do a stupid thing like that?
> The list terminator is de facto a symbol because it's exploited for its identity
First, the list terminator need not be the same thing as the empty list. The list terminator is an implementation thing. The empty list is a language-semantics thing. These need not be the same.
Second, neither of these need to be unique. There can be multiple list terminators, and there can be multiple empty lists, just as there can be multiple empty vectors and multiple empty strings and even multiple instances of the same number (which actually happens with bignums).
> we care whether the cdr of a cons is or is not nil
No, what we care about -- or at least what we should care about -- is whether the CDR of a cons is an (n.b. not the) empty list. (eq #() #()) need not be true (in fact, generally isn't). Why should (eq () ()) be any different?
And BTW under no circumstances should taking the CAR or CDR of an empty list do anything other than signal an error.