Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

But why is a list not a list across all platforms?

Other Lisp dialects run on top of the JVM, .net, JavaScript, LLVM, UNIX, iOS, Android, Linux, ... and there is no such problem.

Try ABCL on the JVM. (listp (cons 1 '(2 3))) is T.

I would think that consistency and correctness of basic Lisp operations (-> LISP stands for "List Processor") would be a high priority of any self-respecting Lisp dialect?



Alright, your concern about consistency in basic operations is valid. Yet, it's important to consider the different design philosophies and practical constraints of Clojure and Clojurescript - platform-specific optimizations, type systems, practical trade-offs, etc.

To be honest, I never knew about this (list?) thing behaving differently with (cons) because I personally have never stumbled upon that. Most likely, people raised this question because, like I said, the language has been around for decades. I don't know the proper and accurate answer for you here, that's probably some intentional design choice rather than a bug, driven by the need to balance consistency with platform-specific optimizations and constraints; if that was important, I would've found it and known how to explain it. It is not important (think of it as an easter egg), because...

Like I said before, Clojure emphasizes using seq abstraction instead of lists. It is a core part of Clojure's design and works uniformly across Clojure, Clojurescript, Clojure-Dart and other variants. It abstracts over different collection types, allowing you to work with sequences without worrying about the underlying concrete types. Functions that return `seq` often produce lazy sequences, which can be more memory-efficient and allow for incremental processing. This is particularly useful for handling large datasets or infinite sequences. `seq` functions, such as `first`, `rest`, `next`, and higher-order functions like `map`, `filter`, and `reduce`, provide a consistent API and behavior across different implementations of Clojure. This minimizes the impact of platform-specific differences. In addition, it can often lead to performance optimizations tailored to the specific platform, as it allows the runtime to use the most efficient way to traverse collections.

Yes, Lisp traditionally stands for "LISt Processing," and lists are indeed central to Lisp's heritage. However, modern Lisp dialects have evolved to handle a broader range of data structures efficiently while maintaining the core principles of Lisp. Common Lisp, while having first-class support for lists, also has a broader set of data structures including vectors, strings, and bytevectors. Racket enhances the language with a diverse range of data structures—vectors, hash maps, sets, and streams. Elisp, too, has hash tables and strings. Clojure adds vectors, maps, and sets as first-class citizens. I guess that's why we no longer use "Lisp" as an acronym - we no longer capitalize each letter. I guess one could say "it is still 'LISt Processing' because every expression written in it is a list..." ¯\_(ツ)_/¯


LISP does not stand for "List processing", it stands for "List Processor". To quote John McCarthy: "A programming system called LISP (for LISt Processor) has been developed for the IBM 704...".

This is a programming system, which is a list processor. Not a vector processor, not a graphics processor, not a number cruncher -> it's a list processor.

Now McCarthy defines three basic LISP features:

* Data has the form of S-expressions, for which a definition is given, which is linked lists of atoms&lists. -> a very low level definition.

* There is a LISP language which processes these s-expressions. Simple operators are being defined.

* LISP can interpret and execute LISP programs in the form of s-Expressions.

To sum up: there are linked lists, a programming language for processing them AND the programs themselves can be interpreted by LISP.

He then defines an "universal LISP function", an interpreter for LISP in LISP.

That's the actual core essence of LISP. Think about it!

That's the big thing which was defined in the very first pages of the first LISP papers: a very simple foundation, which describes how to do computation by a List Processor, applied to itself.

This is the core language: a data structure, a few operations and an interpreter written in itself.

Fire up your Emacs Lisp (start GNU Emacs and do m-x ielm). It's all there: the data structure (-> linked lists of symbols), the operations (car, cdr, cons, read, print, cond, lambda, eq, assoc, append, ...) and the interpreter (-> eval).

It's not one of many data structures, it is the central thing: s-expressions, programs as s-expressions, a list processor (-> EVAL) defined in the language it is executing, executing programs which are given as s-expressions, able to execute itself.

All the other stuff you've mentioned is secondary: Homoiconicity, First-class Functions, Macro System, automated memory management, REPL-driven, interactive development.

McCarthy described a specific way to do computation. That's the core of LISP. One which can be taught in a day, which fits on a few pages and which allows us to develop a simple mental model of it.

Clojure does not have the same axiomatic definition of a List Processor, it is not interpreting lists, ... It has a complex data structure at its core: lazy persistent sequences, it compiles code (the compiler is written in Java and compiles to the Java Virtual Machine), it has the JVM underneath, ...

If I break the execution of a list processor, I see the programs as lists being executed by a lisp processor. Write a program in Emacs Lisp, execute it by the interpreted EVAL, run it, interrupt it and you'll see a backtrace where the interpreter runs the Lisp expressions.

If I want to teach people Lisp, I explain this simple LISP PROCESSOR. That's the mental model they need to have: the form of Lisp programs and its execution. Once they have this mental model of LISP, it is valid for all core LISP dialects.

This is also explained in the LISP literature: "LISP 1.5 manual", "Anatomy of LISP" (John Allen -> the book is available here for download: https://dl.acm.org/doi/10.5555/542865 ), "Structure and Interpretation of Computer Programs" (Abelson/Sussman), "Paradigms of AI Programming" (Norvig), "Lisp In Small Pieces" (Queinnec), "The roots of LISP" (Graham) and others. All these explain this foundation (and expand on it).

Clojure is slightly different. It's a modern&pragmatic&opinionated dialect of Lisp for the JVM, which does not have this simple LIST PROCESSOR. That's why it is an dialect of LISP, but not one which shares the axiomatic core of LISP.


While Clojure emphasizes lazy persistent sequences, it still uses lists as a primary data structure and supports list processing. The new data structures are extensions, not replacements, designed to address specific application needs more effectively.

Many classical Lisp implementations also compile code. Compilation does not preclude a language from being a Lisp. Clojure continues the use of s-expressions for code and data representation. Clojure provides a REPL, a staple of Lisp environments.

Clojure retains the homoiconic nature of Lisp. Macros in Clojure very much akin to those in traditional Lisps.

Clojure’s design choices are about extending Lisp principles to solve modern software engineering challenges. Rich Hickey wasn't like: "Lispers were all wrong..." He chose to build the language on top of Lisp principles, and these principles are evident, unmistakably clear and can be seen with a naked eye.

I'm honestly getting worn out by this debate. You're delving into the minutiae of taxonomy, like whether early hominids can be classified as humans, and it's just exhausting. I've told you before, and I'll tell you again — unless someone truly eminent persuades Rich and he eventually concedes, saying: "Yes, Clojure in reality ain't no Lisp..." only then will I alter my position. Until that time, I will continue to call it a Lisp, regard it as a Lisp, and talk about it as a Lisp. The only thing you can do to change my opinion - is to persuade Rich to change his.


You are on the surface. I'm talking about a computing paradigm defined by LISP. A foundational model at the core of the language, enabling us to understand how the List Processor actually works.

LISP has dialects which are not homoiconic, where programs are not written as s-expressions, ... But the core is the same: a specific List Processor. McCarthy himself did not want to write programs as s-expressions. He defined M-Expressions, where S-Expressions were only used for the data.

Check the book "Anatomy of LISP", which is a classic. All the code in the book is written in M-Expressions. Now, Racket gets a new syntax (-> Rhombus). But the language core mechanisms are still there. I'm a fan of the s-expression syntax, but I've also heard&read from many people (incl. McCarthy), that THEY preferred a different syntax. I've seen Apple defining Dylan (Dynamic Language) with an s-expression syntax and then changing the language to a different syntax for broader adoption. You (and me) think it is essential. McCarthy and others thought that it was the wrong syntax for getting wider adoption.

Btw., personally, I don't care that much what Rich Hickey says. I can think for myself.


It is fallacious to suggest that in order to have generic iteration that supports non-list objects, we must abandon the classic list processing functions and give them alternative names and behaviors.

In the TXR Lisp dialect, classic functions like mapcar iterate over nonlists, without losing a shred of backward compatibility over lists.

For instance, we can iterate starting from an integer, in parallel with iterating over a list:

  1> (mapcar 'cons '(a b c) 0)
  ((a . 0) (b . 1) (c . 2))
What we do is pattern the iteration abstraction after the car/cdr model, so that car/cdr iteration falls out as a straightforward special case.

To do this we define an iteration API with four operations:

  iter-begin: construct the iterator from the sequence object
  iter-more: test the iterator whether it has more elements
  iter-item: if iter-more tested true, get the first item
  iter-step: calculate iterator of rest of sequence
iter-step may be functional or destructive, so the caller must capture the new iterator returned as a value and must stop using the old one.

Integer:

  1> (iter-begin 0)   ;; identity
  0
  2> (iter-more 0)    ;; true function: ignores argument, returns t.
  t
  3> (iter-item 0)    ;; identity
  0
  4> (iter-step 0)    ;; successor function
  1
String:

  5> (iter-begin "abc")   ;; opaque iterator returned
  #<seq-iter: a0bef50>
  6> (iter-more *5)
  t
  7> (iter-item *5)
  #\a
  8> (iter-step *5)       ;; destructively stepped
  #<seq-iter: a0bef50>
  9> (iter-item *8)       ;; let's refer to *8 anyway
  #\b
List:

  10> (iter-begin '(1 2 3))   ;; identity, like in integer case
  (1 2 3)
  11> (iter-more '(1 2 3))    ;; not-equal-to-nil test
  t
  12> (iter-item '(1 2 3))    ;; car
  1
  13> (iter-step '(1 2 3))    ;; cdr with check
  (2 3)
  14> (iter-step '(1 . 2))    ;; demo of check
  ** iter-step: 2 is not a cons
Without the check in (iter-step '(1 . 2)) we would get 2, and that would then iterate through 2, 3, 4, ...


> Rich Hickey wasn't like: "Lispers were all wrong..."

Actually, yes he was.


Citation?




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: