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.
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.
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.
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!
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."
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.
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.
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.
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.)
> 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.
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).
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.
(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)
> 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.
> 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.
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.
$
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!)
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.
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.
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.
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.
> 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?
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.
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.)
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.
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.
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.
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:
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.
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.
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?
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.
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.
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.
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.
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.
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.
https://it.m.wikipedia.org/wiki/Ricercare
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.
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.
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!
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."
But I'd love to try! Maybe I'll take an online class for fun.
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.)
> 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.
I believe we've been getting a bit of the latter.
Deleted Comment
[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)
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.
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!)
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.
https://en.m.wikipedia.org/wiki/CAR_and_CDR
In any case, ASTute observation
er, ASTute ;)
Deleted Comment
Scheme doesn't. Taking the CAR or CDR of nil is an error.
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.
Which is a shame, because I prefer (Guile) Scheme to Common Lisp.
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?
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.
Just call it a Result::Failure monad, say you meant to do that, and confuse legions of programmers for decades.
Deleted Comment
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.
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.
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.
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
Yeah. XML and S expressions are pretty close to functionally equivalent. But once you've seen S expressions, XML is disgustingly clumsy.
Doesn't win any price, or content worth of "I rewrote X in Y" blogpost, but does the job.
https://news.ycombinator.com/item?id=41822811
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?
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.
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.
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