5 ms·
> Attempting to take the car or cdr of nil causes (or should cause) the Lisp genie to cough out an error message, just as attempting to divide by zero should ev
by susam 2y ago
> Attempting to take the car or cdr of nil causes (or should cause) the Lisp genie to cough out an error message, just as attempting to divide by zero should evoke an error message.
Interestingly, this is no longer the case. Modern Lisps now evaluate (car nil) and (cdr nil) to nil. In the original Lisp defined by John McCarthy, indeed CAR and CDR were undefined for NIL. Quoting from <https://dl.acm.org/doi/pdf/10.1145/367177.367199 https://dl.acm.org/doi/pdf/10.1145/367177.367199>:
> Here NIL is an atomic symbol used to terminate lists.
> car [x] is defined if and only if x is not atomic.
> cdr [x] is also defined when x is not atomic.
However, both Common Lisp and Emacs Lisp define (car nil) and (cdr nil) to be nil. Quoting from <https://www.lispworks.com/documentation/HyperSpec/Body/f_car_c.htm https://www.lispworks.com/documentation/HyperSpec/Body/f_car...>:
> If x is a cons, car returns the car of that cons. If x is nil, car returns nil.
> If x is a cons, cdr returns the cdr of that cons. If x is nil, cdr returns nil.
Also, quoting from <https://www.gnu.org/software/emacs/manual/html_node/elisp/List-Elements.html https://www.gnu.org/software/emacs/manual/html_node/elisp/Li...>:
> Function: car cons-cell ... As a special case, if cons-cell is nil, this function returns nil. Therefore, any list is a valid argument. An error is signaled if the argument is not a cons cell or nil.
> Function: cdr cons-cell ... As a special case, if cons-cell is nil, this function returns nil; therefore, any list is a valid argument. An error is signaled if the argument is not a cons cell or nil.
- susam 2y agoI was curious what it is like on Maclisp. Here is a complete telnet session with Lars Brinkhoff's public ITS: $ telnet its.pdp10.se 10003 Trying 88.99.191.74... Connected to pdp10.se. Escape character is '^]'. Connected to the KA-10 simulator MTY device, line 0 ^Z TT ITS.1652. DDT.1548. TTY 21 3. Lusers, Fair Share = 99% Welcome to ITS! For brief information, type ? For a list of colon commands, type :? and press Enter. For the full info system, type :INFO and Enter. Happy hacking! :LOGIN SUSAM TT: SUSAM; SUSAM MAIL - NON-EXISTENT DIRECTORY :LISP LISP 2156 Alloc? n * (status lispversion) /2156 (car nil) NIL (cdr nil) NIL ^Z 50107) XCT 11 :LOGOUT TT ITS 1652 Console 21 Free. 19:55:07 ^] telnet> ^D Connection closed. $
- dokyun 2y agoI recall reading that in early versions of Maclisp, taking the CAR or CDR of NIL worked differently: Taking its CAR would signal an error as you would expect, however taking its CDR would return the symbol plist of NIL, as internally the operation of CDR on the location of a symbol would access its plist, and that's how it was commonly done before there was a specific form for it (and it actually still worked that way into Lisp Machine Lisp, provided you took the CDR of the locative of a symbol). Apparently the behaviour of the CAR and CDR of NIL being NIL was from Interlisp, and it wasn't until the designers of Maclisp and Interlisp met to exchange ideas that they decided to adopt that behaviour (it was also ostensibly one of the very few things they actually ended up agreeing on). The reason they chose it was because they figured operations like CADR and such would be more correct if they simply returned NIL if that part of the list didn't exist rather than returning an error, otherwise you had to check each cons of the list every time. (If somebody can find the source for this, please link it!)
- pfdietz 2y agoBut of course cadr still has to check each access, to see if its of type (or cons null). So I don't see what was saved.
- dokyun 2y agoIt would be considered "the right thing" to do something that's so common you probably want it without asking. I don't think CADR would check for NIL since it's meant to be equivalent to (car (cdr x)), so if you wanted a safe list operation you would have to check it like this: (I'll use CADADR because it makes the issue more apparent) (and (car x) (cadr x) (cadar x) (cadadr x)) You would have to write this every time you want to see if there's a really CADADR, whereas if CAR and CDR can return NIL then you can just write (cadadr x) and CADADR can still be defined as (car (cdr (car (cdr x)))) and have the desired behaviour.
- pfdietz 2y agoAny argument of the form "you have to write this idiom" is covered by "so either it doesn't happen often, or one can use a macro". There's cognitive overhead to using a macro, but there's also cognitive overhead to remembering car and cdr work on nil. The latter is already paid for, so changing Common Lisp now doesn't make sense, but in an alternate world with a different design it would be a point.
- sph 2y agoSadly this is not the case with Scheme and it makes for very unergonomic code, especially for a newbie like me. Which is a shame, because I prefer (Guile) Scheme to Common Lisp.
- pfdietz 2y agoI'm very tied to Common Lisp, but I'm perfectly fine with the idea of a lisp in which car and cdr would be undefined on nil. Also, I'd be fine with a lisp in which () is not a symbol. I don't think these features of Common Lisp are essential or all that valuable.
- sph 2y agoThey are not essential, but they make code that operates in lisp more compact and pleasant to write. In Scheme my code is littered with (if (null? lst) ;; handle empty case here ...) Simply because otherwise car throws an error. This whole section is often unnecessary in CL.
- tmtvl 2y agoBut you need to handle the empty case anyway otherwise you process nils ad infinitum.
- erik_seaberg 2y agoYou can say (if lst ...) if the empty list is falsy, but Scheme eventually chose to add #t and #f. Oddly #f is the only false value but #t is not the only true value.
- tmtvl 2y agoI do prefer nil being the false value as well as the empty list, even if it makes it more awkward to distinguish between 'there is a result, but the result is an empty list' and 'there are no results'. But that has nothing to do with car and cdr in Common Lisp treating nil as though it were `(cons nil nil)'. The only value in that I can see is would be if rplaca and rplacd can do some useful things with that (so `(setf (car symbol-that-currently-points-at-nil) foo)' and `(setf (cdr stcpat) bar)' do those useful things).
- dkarl 2y agoThe use of car and cdr are such a surprisingly concrete implementation detail in the birth of a language that was designed to be mathematical. The most basic and famous operators of "List Processor" were created to operate not on lists but on conses, an element in a particular machine representation that Lisp uses to build data structures! Not only are conses not always interpreted as lists, but a very very important list, the base case for recursive functions on lists, is not represented by a cons. Sixty years later, most Lisp programs are still full of operations on conses. A more accurate name for the language would be "Cons Processor!" It's a reminder that Lisp was born in an era when a language and its implementation had to fit hand in glove. I think that makes the achievement of grounding a computer language in mathematical logic all the more remarkable.
- fuzztester 2y agomaybe related to the need to conserve CPU registers in machines of the time? https://en.m.wikipedia.org/wiki/CAR_and_CDR https://en.m.wikipedia.org/wiki/CAR_and_CDR In any case, ASTute observation er, ASTute ;)
- deleted 2y ago[deleted]
- lisper 2y ago> Modern Lisps now evaluate (car nil) and (cdr nil) to nil. Scheme doesn't. Taking the CAR or CDR of nil is an error.
- anthk 2y agoElisp and CL do.
- susam 2y agoDoes Scheme even have NIL in the sense that other Lisps like CL or Elisp have? I mean in Common Lisp, we have: CL-USER> (symbolp nil) T CL-USER> (atom nil) T CL-USER> (listp nil) T Similar results in Emacs Lisp. But in MIT Scheme, we get: 1 ]=> nil ;Unbound variable: nil Of course, we can use () or (define nil ()) to illustrate your point. For example: 1 ]=> (car ()) ;The object (), passed as the first argument to car, is not the correct type. But when I said NIL earlier, I really meant the symbol NIL that evaluates to NIL and is both a LIST and ATOM. But otherwise, yes, I understand your point and agree with it.
- dokyun 2y agoI don't believe so, standardly. Guile scheme added the value `#nil' which is equivalent to NIL and distinct from #f and the empty list, but this was done in order to support Emacs Lisp.
- lisper 2y ago> Does Scheme even have NIL in the sense that other Lisps like CL or Elisp have? No. It has an empty list, which is a singleton atomic value whose type is not shared with any other object, and it has a boolean false value, which is distinct from the empty list. A user can create a symbol named NIL, but that symbol has no characteristics that distinguish it from any other symbol. You can, of course, bind NIL to either the empty list or boolean false (or any other value) but it can only have one value at a time (per thread).
- blenderob 2y ago
- lisper 2y agoThere really should be two different kinds of cons cells, one for "proper" linked lists and another for general purpose consing. The difference is that the cdr of the first kind of cons cell (I'll call it a PL-cons) can only be NIL or another PL-cons, not anything else. This would eliminate vast categories of bugs. It would also make the predicate for determining is something was a proper list run in constant time rather than O(n). (There would still be edge cases with circular lists, but those are much less common than non-proper lists.)
- pmarreck 2y agois there a term to describe the language design choice (reminds me of SQL, btw, where it is equally bad IMHO) where doing things to nil just returns nil without erroring? I want to call it "bleeding nils/NULLs" if there isn't another term yet. As stated, I think this design choice is terrible, especially if nil isn't equivalent to false in boolean comparisons (as it is in Ruby and Elixir- with Elixir actually providing two types of boolean operators with slightly different but significant behavior; "and" will only take pure booleans while "&&" will equate nil with false). It might mean cleaner-written code upfront but it's going to result in massively-harder-to-debug code because the actual error (a mishandled nil result) might only create a visible problem many stack levels away in some completely different part of the code.
- michaelcampbell 2y ago> where doing things to nil just returns nil without erroring Just call it a Result::Failure monad, say you meant to do that, and confuse legions of programmers for decades.
- pmarreck 2y agoLOL. I mean, that's in essence what it's trying to do, right?
- deleted 2y ago[deleted]