←back to thread

Hofstadter on Lisp (1983)

(gist.github.com)
372 points Eric_WVGG | 1 comments | | HN request time: 0s | source
Show context
susam ◴[] No.41861244[source]
> 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>:

> 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...>:

> 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/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.

replies(6): >>41861327 #>>41861751 #>>41862379 #>>41862873 #>>41862933 #>>41868929 #
sph ◴[] No.41861751[source]
Sadly 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.

replies(2): >>41862075 #>>41863892 #
BoiledCabbage ◴[] No.41863892[source]
> Sadly this is not the case with Scheme and it makes for very unergonomic code,

How so? If car of nil returns nil, then how does a caller distinguish between a value of nil and a container/list containing nil?

The only way is they can check to see if it's a cons pair or not? So if you have to check if it's a cons pair then you're doing the same thing as in scheme right?

I may be missing something, but isn't it effectively the same amount of work just potentially? Need to check for nil and need to check if it's a pair?

replies(2): >>41864352 #>>41893182 #
susam ◴[] No.41864352[source]
> How so? If car of nil returns nil, then how does a caller distinguish between a value of nil and a container/list containing nil?

How about this?

  CL-USER> (null nil)
  T
  CL-USER> (null '(nil))
  NIL
  CL-USER>
replies(1): >>41865006 #
BoiledCabbage ◴[] No.41865006[source]
I think that's my point. You still need a separate call to distinguish the nil rom the list of nil case.

At that point, if you're making the two calls how is LISP's behavior any more ergonomic than Scheme. I'm not saying it's not possible, I just don't see it.

Can you show code between the two and how one is much worse than the other?

replies(2): >>41870434 #>>41893234 #
1. kazinator ◴[] No.41893234[source]
We can declare that our code only works with lists of numbers, or lists of strings. Therefore, nil is not expected. If (car list) returns nil, it can only be that the list is empty, because if it were not empty, it would return a number, or string, or widget or whatever the list holds.

Even when we have a heterogeneous list in Lisp, like one that can have symbols, numbers, strings or widgets, we can almost always exclude nil as a matter of design, and thus cheerfully use the simpler code.

We cannot exclude nil when a list contains Boolean values, because nil is our false.

We also cannot exclude it when it contains lists, because nil is our empty list.

The beauty is that in many situations, we can arrange not to have to care about the distinction between "item is missing" and "item is false" and "item is an empty list", and then we can write terser code.

When you see such terse code from another programmer, you know instinctively what the deal is with how they are treating nil before even looking at any documentation or test cases.