Readit News logoReadit News
susam · a year ago
In case anyone else is confused by what the functions named "oval" and "snot" mean in the following example:

  > (cond ((eq (oval pi) pie) (oval (snot pie pi)))
  (t (eval (snoc (rac pi) pi))))
I realised after a few seconds that they are meant to be "eval" and "snoc" instead. The above code should be written as the following instead:

  (cond ((eq (eval pi) pie)
         (eval (snoc pie pi)))
        (t (eval (snoc (rac pi) pi))))
This article has been a fascinating read, by the way. Kudos to the maintainer of the Gist post. I am also sharing these corrections as comments on the Gist post.

EDIT #1: Downloaded a copy of the original Scientific American article from https://www.jstor.org/stable/24968822 and confirmed that indeed the functions "oval" and "snot" are misspellings of "eval" and "snoc".

EDIT #2: Fixed typo in this comment highlighted by @fuzztester below.

fuzztester · a year ago
>confirmed that indeed the functions "oval" and "snot" are misspellings of "eval" and "snot".

Correction of your correction:

confirmed that indeed the functions "oval" and "snot" are misspellings of "eval" and "snoc".

And I guess snoc is cons reversed and rac is car reversed.

susam · a year ago
> Correction of your correction

Thanks! Fixed.

> And I guess snoc is cons reversed and rac is car reversed.

Indeed! That's exactly how those functions are introduced in the article. Quoting from the article:

> The functions rdc and snoc are analogous to cdr and cons, only backwards.

hinkley · a year ago
OCR maybe?
lionkor · a year ago
maybe LLM "reformat/rewrite this"?
bsder · a year ago
This article simply reinforces that the primary problem with the popularity of Lisp was people explaining Lisp.

This article, like every other Lisp article, tells pre-teen me nothing that he could use. Nobody ever demonstrated how much easier task X is in Lisp over asm/C/Pascal/etc.

By contrast, current me could have told pre-teen me "Hey, that spell checker that took you 7 months to write in assembly? Yeah, it's damn near trivial in Lisp on a microcomputer with bank switched memory that nobody every knew how to utilize (it makes garbage collection completely deterministic even on a woefully underpowered CPU). Watch."

I want to weep over the time I wasted doing programming with the equivalent of tweezers, rice grains and glue because every Lisp article and textbook repeated the same worn out lists, recursion and AI crap without ever demonstrating how to do anything useful.

troupe · a year ago
Common Lisp: A Gentle Introduction to Symbolic Computation might be useful for the context you are describing. https://www.cs.cmu.edu/~dst/LispBook/
_19qg · a year ago
Practical Common Lisp https://gigamonkeys.com/book/
bsder · a year ago
Didn't exist back then. Likewise SICP first edition was 1996.

I did have a copy of "LISP: A Gentle Introduction to Symbolic Computation" by Touretzky in 1986. It wasn't really that much better than any of the articles. It never explained why using Lisp would be so much easier than anything else even for simple programming tasks.

Had some of the Lisp hackers deigned to do stuff on the piddly little micros and write it up, things would look a whole lot different today.

Maybe there was a magazine somewhere doing cool stuff with Lisp on micros in the 1980-1988 time frame, but I never found it.

abecedarius · a year ago
Hofstadter's followup article had a more interesting example.

When I first got a Byte magazine as a pre-teen, one of the articles was Lisp code for symbolic differentiation and algebraic simplification. I really couldn't follow it but felt there was something intriguing there. Certainly it wouldn't have been easier in Basic.

(Byte September 1981, AI theme issue. Later I was able to tell the code was not so hot...)

I didn't really get into Lisp until the late 80s with XLisp on a PC, and SICP. Worth the wait!

oaktowner · a year ago
I just love his writing so much -- he captures what I felt when I discovered Lisp. As a kid learning programming in the 80s, I had already done some BASIC, Fortran, Pascal and COBOL in high school and early college. There were differences, of course, but they had some fundamental commonality.

At UC Berkeley, however, the first computer science class was taught in Scheme (a dialect of Lisp)...and it absolutely blew me away. Hofstadter is right: it feels the closest to math (reminding me a ton of my math theory classes). It was the first beautiful language I discovered.

(edit: I forgot to paste in the quote I loved!)

"...Lisp and Algol, are built around a kernel that seems as natural as a branch of mathematics. The kernel of Lisp has a crystalline purity that not only appeals to the esthetic sense, but also makes Lisp a far more flexible language than most others."

Jeff_Brown · a year ago
Have you tried Haskell? It feels much closer to math to me. Definitions, not procedures. It even looks like math.
medo-bear · a year ago
Maybe Haskell is more like Bourbaki math, whereas Lisp is more like Russian style maths (ala Vladimir Arnold). I prefer the latter tbh, and I come to programming from a maths background. We are all different. Lisp to me is yet to be surpassed in terms of ergonomics when transfering my thoughts into computer code.
oaktowner · a year ago
No! After about 10 years of writing software professionally, I moved over to product management, and my time spent coding decreased drastically (in the last 15 years, only some Python to show my kids a thing or two).

But I'd love to try! Maybe I'll take an online class for fun.

tightbookkeeper · a year ago
Personal anecdote: I got a lot more out of lisp that stuck with me than Haskell. Occasionally I say "oh this is a monad" or think about a type signature, but that's about it.
nxobject · a year ago
At the risk of diverging off from the original post, I also think that calling it "math" might make things a bit murky (and this is coming from someone who wanted to be algebraic topologist!)

It _is_ an elegant and minimal expression of a style of programming that is ubiquitous among dynamically-typed, garbage-collected languages. And it's a "theory" in the sense that it seems complete, and that you can think of ways to solve problems into Scheme and translate that into other dynamically-typed languages and still end with an elegant solution. Emphasis on the elegant (since minimal, wart-free, consistent and orthogonal, etc.).

Scheme was a simplification and a "cleaning up" compared to conventional Lisps of the time (lexical scoping, single shared namespace for functions and variables etc.)

furyofantares · a year ago
40 years ago, and 20 years into the field:

> February, 1983

> IN previous columns I have written quite often about the field of artificial intelligence - the search for ways to program computers so that they might come to behave with flexibility, common sense, insight, creativity, self awareness, humor, and so on.

This is very amusing to me because it reads like a list of things LLMs truly stink at. Though at least they finally represent some nonzero amount of movement in that direction.

zyklu5 · a year ago
You must interact with more interesting people than I because to me LLMs have demonstrated as much "common sense, insight, creativity, self awareness, humor" as the average person I run into (actually maybe more but that makes me sound crazy to myself).
ska · a year ago
Eliza effect is alive and well, also.
ableal · a year ago
Way back someone observed that the problem was not computers thinking like people, but people thinking like computers.

I believe we've been getting a bit of the latter.

Deleted Comment

ska · a year ago
His research group has a long history of trying to tackle these problems. Some interesting reading , even if much of it hasn’t (yet?) panned out .
kevindamm · a year ago
This article, and the two companion articles it mentions, can be found in the book "Metamagical Themas" [0] in chapters 17-19, as well as all of his other articles that appeared in this series of Scientific American.

[0]: https://www.goodreads.com/book/show/181239.Metamagical_Thema...

(the book's title is the article series, which originated as an anagram of the article series that Martin Gardner authored, "Mathematical Games," also published in Scientific American and which Hofstadter then took over)

antitoi · a year ago
I love this book. Highly recommended for all lovers of Godel Escher Bach (his classic).
onemoresoop · a year ago
This book is indeed very beautiful to read and look at, it has a lot of fascinating illustrations.
susam · a year 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>:

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

susam · a year ago
I 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 · a year ago
I 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!)

dkarl · a year ago
The 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 · a year ago
maybe related to the need to conserve CPU registers in machines of the time?

https://en.m.wikipedia.org/wiki/CAR_and_CDR

In any case, ASTute observation

er, ASTute ;)

Deleted Comment

lisper · a year 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.

susam · a year ago
Does 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.

pmarreck · a year ago
I'm not a LISPer but this just seems more correct to me, since stricter is usually more correct.

Ruby (not a lisp but bear with me) started to do this more correctly IMHO where a nil would start throwing errors if you tried to do things with it BUT it would still be equivalent to false in boolean checks.

anthk · a year ago
Elisp and CL do.
sph · a year ago
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.

pfdietz · a year ago
I'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.
BoiledCabbage · a year ago
> 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?

pmarreck · a year ago
is 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 · a year 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.

Deleted Comment

lisper · a year ago
There 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.)
smrq · a year ago
I certainly know the Lisp information in this article already, but it's still a fun read. Hofstadter just has a charming way with words.

I found this bit extra amusing:

>It would be nice as well as useful if we could create an inverse operation to readers-digest-condensed-version called rejoyce that, given any two words, would create a novel beginning and ending with them, respectively - and such that James Joyce would have written it (had he thought of it). Thus execution of the Lisp statement (rejoyce 'Stately 'Yes) would result in the Lisp genie generating from scratch the entire novel Ulysses. Writing this function is left as an exercise for the reader.

It took a while, but we got there. I don't think 2024's AI is quite what he had in mind in 1983, but you have to admit that reproducing text given a little seeding is a task that quite suits the AI of today.

taeric · a year ago
I do think LISP remains the major language that can encompass the strange loop idea he explored in his work. I know LISP is not the only homoiconic language, but it is the biggest that people know how to use where the "eval" function doesn't take in a string that has to be parsed.

I hate that people are convinced LISP == functional programming, writ large. Not that I dislike functional programming, but the symbolic nature of it is far more interesting to me. And it amuses me to no end that I can easily make a section of code that is driven by (go tag) sections, such that I can get GOTO programming in it very easily.

nine_k · a year ago
Another (properly functional) homoiconic language that enjoyed mainstream adoption briefly in '00s is XSLT. Its metaprogramming features were rather widely used, that is, producing an XSLT from XSLT and maybe some more XML, instead of hand-coding something repetitive, was rather normal.

The syntax was a bigger problem than Lisp's syntax, though.

It's not easy to produce a language with a syntax that's good as daily use syntax, but is also not unwieldy as an AST. Lisp is one of the few relatively successful examples.

pmarreck · a year ago
I don't know how many other languages use it but I've long admired Elixir's approach to giving devs access to the AST using its basic types in order to write macros:

https://hexdocs.pm/elixir/macros.html

It is certainly possible to implement this sort of thing in other languages, I think, depending on the compilation or preprocessing setup

apex_sloth · a year ago
Thanks for this little flashback to when I had to write XSLT for apache cocoon as my student job
AnimalMuppet · a year ago
> The syntax was a bigger problem than Lisp's syntax, though.

Yeah. XML and S expressions are pretty close to functionally equivalent. But once you've seen S expressions, XML is disgustingly clumsy.

pjmlp · a year ago
For a while that is how I made my Website dynamic, by writing everything in XML and linking XSLT stylesheets, however the future ended up not being XHTML, and eventually I rewrote those stylesheets in PHP.

Doesn't win any price, or content worth of "I rewrote X in Y" blogpost, but does the job.

throwaway19972 · a year ago
Not to mention specifically with Scheme and continuation-oriented programming, the line between functional and non-functional programming becomes so blurry as to become nearly meaningless.
fuzztester · a year ago
The definition of functional programming is itself quite blurry, says Chris Lattner (of Swift, LLVM, Mojo), in this talk I posted here recently:

https://news.ycombinator.com/item?id=41822811

medo-bear · a year ago
Even the definition of a lisp is blurry when we zoom in to find the seperation boundry
brucehoult · a year ago
Lambda: the ultimate GOTO
bbor · a year ago
I love and relate to any impassioned plea on SWE esoterica, so this seems like as good of a place as any to ask: What, in practice, is this deep level of "homoiconic" or "symbolic" support used for that Python's functools (https://docs.python.org/3/library/functools.html) doesn't do well? As someone building a completely LISPless symbolic AGI (sacrilege, I know), I've always struggled with this and would love any pointers the experts here have. Is it something to do with Monads? I never did understand Monads...

To make this comment more actionable, my understanding of Python's homoiconic functionality comes down to these methods, more-or-less:

1. Functions that apply other functions to iterables, e.g. filter(), map(), and reduce(). AKA the bread-n-butter of modern day JavaScript.

2. Functions that wrap a group of functions and routes calls accordingly, e.g. @singledispatch.

3. Functions that provide more general control flow or performance conveniences for other functions, e.g. @cache and and partial().

3. Functions that arbitrarily wrap other functions, namely wraps().

Certainly not every language has all these defined in a standard library, but none of them seem that challenging to implement by hand when necessary -- in other words, they basically come down to conviences for calling functions in weird ways. Certainly none of these live up to the glorious descriptions of homoiconic languages in essays like this one, where "self-introspection" is treated as a first class concern.

What would a programmer in 2024 get from LISP that isn't implemented above?

taeric · a year ago
I'm basically a shill for my one decent blog post from a while back. :D

https://taeric.github.io/CodeAsData.html

The key for me really is in the signature for "eval." In python, as an example, eval takes in a string. So, to work with the expression, it has to fully parse it with all of the danger that takes in. For lisp, eval takes in a form. Still dangerous to evaluate random code, mind. But you can walk the code without evaluating it.

wk_end · a year ago
The syntax of Lisp is made up of the same fundamental data types as you use when writing Lisp programs. `(+ 1 2 3)` is both a Lisp expression that evaluates to 6 and also a list containing four items, the symbol `+` and the numbers 1, 2, and 3.

In general, we can say that the Lisp language is very good at manipulating the same data types that the syntax of Lisp programs is made from. This makes it very easy to write Lisp programs that swallow up Lisp programs as raw syntax, analyze Lisp programs syntactically, and/or spit out new Lisp programs as raw syntax.

kazinator · a year ago
Some of it is because many people's only contact with Lisp is via academia, and the academics who teach it actually don't care about developing anything with Lisp. They use it as a vehicle for concepts, and those concepts typically revolve around functional recursion.

The Scheme language and it surrounding culture are also culprits. Though Scheme isn't functional, it emphasizes pure programming more than its Lisp family predecessors. The basic language provides tail recursive constructs instead of iterative ones, and demands implementations to implement optimized tail calls.

Deleted Comment

Deleted Comment