90 comments

[ 31.2 ms ] story [ 2399 ms ] thread
Last update was 16 years ago. I'm not sure this particular page is in any way relevant in 2018. We could have a discussion about the question posed in the title, and I'm sure some might anyway, but we won't learn anything useful from the posted article.
The premise of the title is a bit odd, because technically anything a lisp can do assembly language can do. The bar on being able to do 'anything' is so low that many things accidentally become Turing complete.

Can lisp programmers still feel smug about using lisp? No, because it never was and never will be productive to feel smug.

Is lisp still the best language for expressing a programmers thoughts? Yes, because it has basically no syntax, so any program will be a direct representation of a programmer's mental model for how their program works. No language with syntax will do that, but the average programmer is probably best actively discouraged from utilising this freedom because they probably have flawed mental models of what is going on.

>> because it has basically no syntax, so any program will be a direct representation of a programmer's mental model for how their program works

That's actually a problem with Lisp, because every programmer would have different mental model and without some syntax rule imposing at least basic structure most of Lisp programs would be PITA to maintain by the unlucky next programmer. It also seriously limits the scale of teams of Lisp programmers (is that even a thing?), because every one of them will have their own "right way" of doing things.

(comment deleted)
This is a criticism that just feels fabricated. Worse, it is all assertion, but no evidence.

By and large, what you are doing is going to dictate large parts of your tool chain. And, by and large, most people have not built tool chains in lisp. I would caution against reading any technical meaning into either of those facts.

Really? You think it's bad for a team of people to have a common understanding of what's what (the syntax)? You can't be serious.
This assumes that lisp leads to that. Which is why the criticism feels fabricated.
From an engineering perspective, you're right, lisp really does give you too much power.

From a linguistic perspective, isn't this the same as accents and dialects developing in isolated groups of people? We don't fault Australians for having unique vocabulary and accent, it's just a different way of speaking. Maybe the beauty of lisp is that each person gets to solve problems in the particular way they feel is most obvious, even if it's not universally accepted as being so.

Imagine it more like everybody is free to make their own dialect without ever having the need to actually talk that dialect with someone else. And then require that a team all with different dialect start to communicate effectively together.
The team should be working in a shared domain-specific language. The good thing about Lisp is that creating a DSL isn't a painful ordeal.
Across my Java career, I've done raw servlets, struts, Spring, Stripe, and back to Spring. The vast majority of my Java knowledge transferred seamlessly from one environment to another with only minor changes ("oh, that's how its done in Stripe").

I could probably switch to another team that uses something else with a different version of Java and become a contributor within a day or two of getting the environment up and running.

If each LISP team is using their own DSL, how much of that LISP knowledge transfers from one shop to another? While the overall structure of the language is the same, how much of the DSL is the same? Is it similarity of Spring to Struts (a few different annotations, all of the data structures are the same)? or is it similarity of Java to C# (different annotations, different data structures, similar syntax, a number of the Patterns shift implementation)?

I call bullshit. The transition you cite easily went through XML configuration to annotation based magic to modern config libraries, where largely people just stopped caring about the magic.

In all, the hard part was internalizing the various life cycles. Not the silly syntactic or configuration tricks to make them happen.

>>If each LISP team is using their own DSL

Think of it more like each team is using their own class naming and method naming schemes.

The remaining software development methodology is same, its just Lisp allows you to create very powerful abstractions and features(Syntactical by default and very definition). Anything you define for the application becomes a part of the language by default because you are writing the program as AST. And there is no syntax.

This enables you to not write things like AbstractClassFactoryFactoryAdapterMySQLDispatcherFacade. Once you have powerful enough features to express your abstractions you don't have to do boilerplate plate work writing tens(hundreds?) of classes to heavy lift simple meta-programming tasks.

A few other features that might interest you.

* Lisp ecosystem encourages you to write incremental code(Bottom up programming). Like you think about something, then go ahead and write it, often prototype in the REPL and test. So you sort of unit test by default.

* Features like Closures, Macros and higher order programming eliminate largely the need for design patterns and boiler plate heavy code.

* Code comes out short, succinct and represents the problem that is being solved. There won't be code which will be solving meta problems. Like you won't be doing opening stream, then buffers, then byte read etc for simply reading a file. In Lisp when you want to read a file, you read a file.

* As mentioned before you will write less boiler plate code for meta-programming, so no need to write hundreds of classes. At that point in time in Java you are busy solving meta problem using things like Dependency injection and frameworks for simply managing configs. In Lisp you will spend more time thinking about problems you are solving, and not meta problems you have to solve in the language, to solve application problems.

* Hot code swapping. You get great debugging facilities, where you can actually pause, introspect buggy code, swap the fix and test. You can't do this in Java.

* Given all these advantages you don't need large teams of programmers, so you communicate and build efficiently. You build big software with smaller teams. Think 10x reduction in team sizes. And that much reduction in Project management overhead, and other associated people operations.

In short If I have to tell you. In Java you think a lot of meta problems trying to work around limitations in the language using DI, annotations, frameworks etc. In Lisp you think only about the application problem.

This is the biggest development methodology change.

> If each LISP team is using their own DSL

Lisp already comes with several different 'DSL' built-in:

  * object-system CLOS
  * format strings with control structures
  * LOOP
If one programs in a language with flexible syntax, then a programmer has to learn how to deal with that:

  * what a the basic language extension patterns?
  * how can one debug it?
  * what a typical pitfalls?
  * what is good design?
  * how to document them?
  * how to present syntax errors to the user?
Language extension suddenly is a developer activity and not a language designer activity.

Much of the stuff in the Java-world, which has used DSLs with different syntax (like being based on XML), can be done directly in Lisp.

A Lisp team will need to learn how to deal with that. There are Lisp code-bases which have been maintained for decades. Even the SBCL compiler, which is written in Lisp, has its roots in code from the 80s (the Python compiler of CMUCL - unrelated to the later language Python).

Macros make things more complex, but the fear that macros make team programming or development of long-living code impossible is just wrong.

I think this criticism of lisp is overblown. It’s never idiomatic to introduce a macro unless you really need one. In the clojure community at least, macros are used pretty sparsely. The base language is is expressive enough.
Macros are only tangentally related to mamon's point.
Really? Because apart from macros, Lisp has less syntax and little complexity compared to most languages. The issue mamon was talking about is about different programmers using different mental models or features. If macros aren't overused, this isn't nearly as big of a deal as it is for C++ or Scala.

I should except common lisp here. That is indeed a complex language. But Clojure and Scheme aren't/

No need to except Common Lisp here. I typically find it quite easy to understand other programmers' Common Lisp. It's not a difficult-to-understand language, and neither are macros, properly used (and they are indeed rarely improperly used).
Clojure is a different language - it borrows stuff from Lisp, but it can't run any non-trivial Lisp code from the past decades without more or less rewriting it. Even primitive stuff like math operators work differently - because Clojure mostly uses the math stack of the host language.
Yeah. As a branch from rowang's points, humans are primed from birth to grok complex syntax and specifically leverage that syntax to ingest information streams.

In programming, this is most clearly illustrated by contrasting Lisp and their ilk with Ruby. General considerations of the languages aside, Ruby's syntax is notoriously complex - yet, the language is famous for being one of, if not the most, easiest to read languages currently in use.

Ruby is famous for it's advocates claiming it is the easiest to read language. I have yet to see evidence of said claim.

Sadly, I have seen the opposite. I, and most of my team, dread dealing with the Ruby package at work. Notorious for obscure tricks and terrible callback based system, where it is ridiculously easy to mess things up.

Can you point to some examples with the properties you suggest make Ruby unreadable?
This implies I think it is unreadable as an intrinsic property of the language. A claim I do not understand, and I am not making.

I could point to dense poetry as an example of hard to read English. Or light prose as examples of the opposite. In reality, I'd likely just be picking examples I like. Largely pivoted in the skill of the writer. Not any intrinsic feature of English.

And, ironically, it likely takes training and experience to appreciate either. Programming and writing.

(comment deleted)
Can you point to some specific examples with the properties you suggest make Ruby unreadable?
It's rather jarring to read "Can lisp programmers still feel smug about using lisp? No" followed by "the average programmer is probably best actively discouraged from utilizing this freedom because they probably have flawed mental models".
Why do you think that syntax has anything to do with expressing mental models? If anything, that would seem to inhibit recognition of similar processes.
> Yes, because it has basically no syntax

That's a myth. Lisp has lots of syntax. It just works different from what one might expect. It even has user-level mechanism to implement custom syntax: Macros.

Lisp usually has a two-level syntax: s-expressions as a data-level syntax and on top of that Lisp program syntax: function calls, built-in special operators (block, catch, if, let, progn, tagbody, quote, function, ...) and macros (lots of macros).

The myth that Lisp has no syntax, comes mostly from computer science education where students learn a very simplified pure Lisp as an educational tool. Telling them Lisp has no syntax, then is the usual trick to get the students of the mental hurdle of a different - Lisp on top of s-expressions - syntax. This Lisp is then just enough to write recursive list processing functions - but has nothing to do with real programming in Lisp. Even slightly more advanced (with respect to Lisp usage) texts like SICP mostly don't use macros for syntactic extension and don't explain them.

(comment deleted)
I'm really curious about the benchmarks used for Java (and C++). List processing being 20x slower than C++? Array access being 7x slower than C++?

So yep. A 16 year old benchmark when Java 1.4 was current (Java 5 was 2004 which brought with it a collections rework and a lot of improvements with not everything needing to be synchronized) based on a 20 year old update on something that was written in '91.

> Built-in Support for Lists. Java has the Vector type, which allows for sequences of dynamically varying length

That's ancient history.

> Dynamic Typing. Java attaches run-time type information to instances of the class Object, but not to primitive data elements. However, Java requires a type declaration for every variable. This has some advantages for production code, but has disadvantages for rapid prototyping and evolution of programs. Java does not have a generic/template system that would allow types life Vector<String>, and suffers greatly because of it. Python's object model is the same as Lisp's, but Python does not allow optional type declarations as Lisp does.

More ancient history.

The Java IDE being compared is BlueJ.

Some of those points were valid in '97. Some were interesting in 2002. But languages have changed and Java (1.4 -> Java 8 and beyond), Python (2.1 -> 3.7) and Perl (5.7 -> 5.28.0) have all matured quite a bit since then. And then there's the ideas of functional programming that are seeping into other languages.

This article aged worse than the bumperstickers from '85 that's also currently on the front page.

do you have more modern benchmarks that cover the same ground?
https://benchmarksgame-team.pages.debian.net/benchmarksgame/... is the current benchmark site.

The article links to http://shootout.alioth.debian.org/gp4/benchmark.php?test=all... (which is a dead link).

The earliest wayback machine for the link is https://web.archive.org/web/20060522132352/http://shootout.a... which puts Java at 1.7x C's speed and Lisp at 3.3. It does the same toy programs in the current implementation (rather than the 'hash access', 'reverse lines', 'array access' and 'list processing' benchmarks cited in the article)

The last 2018 crawl of the link is a 301 to https://benchmarksgame-team.pages.debian.net/benchmarksgame/ (the current site).

Note that the current site argues against the benchmarks cited on norvig. In https://benchmarksgame-team.pages.debian.net/benchmarksgame/...

> The best choice of benchmarks to measure performance is real applications… Attempts at running programs that are much simpler than a real application have led to performance pitfalls. Examples include:

> Kernels, which are small, key pieces of real applications

> Toy programs, which are 100-line programs from beginning programming assignments…

> Synthetic benchmarks, which are small, fake programs invented to try to match the profile and behavior of real applications…

> All three are discredited today, usually because the compiler writer and architect can conspire to make the computer appear faster on these stand-in programs than on real applications.

> Note that the current site argues against the benchmarks cited on norvig.

The current site tells you some of the negatives of measuring tiny toy programs (like those shown on the current website) and tells you why in-spite-of-that we're going to measure some tiny toy programs.

As-you-noted the micro micro programs referenced by Norvig were replaced a decade ago.

>>But languages have changed and Java (1.4 -> Java 8 and beyond), Python (2.1 -> 3.7) and Perl (5.7 -> 5.28.0) have all matured quite a bit since then. And then there's the ideas of functional programming that are seeping into other languages.

Apart from Perl no other language has lispy features like CL. Clojure is the only language that comes close. Perl is the only option you have even now if you want to do decent enough higher order practical programming.

https://hop.perl.plover.com/

Also there is big difference between having some token features(like lambdas) bolted on as a after thought and enabling fully blown functional programming which are well designed and integrated into the language.

Apart from Perl, Functional programming looks very half baked, hard and brittle in most of these languages.

If you want a full featured programming language, including both Functional and OO styles, take a look at Perl 6. Its designed to work will all programming styles.

>>This article aged worse than the bumperstickers from '85 that's also currently on the front page.

Every single thing from that article is true. What are you talking about?

> Java has the Vector type, which allows for sequences of dynamically varying length

The Vector type in Java has been not best practice since Java 1.5 when the Collections framework came out. In particular, the Java 1.4 days and before all of the collection classes were synchronized leading to some not insignificant performance hits (even in single threaded applications). ArrayList and LinkedList are the classes that came with the rework. In particular, while Vector implements List, its backed by an array and so various list like operations (like adding a large number of elements without presizing the array) can take a performance hit.

> Automatic Storage Management. Java and Python support this. Lisp implementations tend to be more mature and perform better.

There have been numerous changes to the JVM for garbage collection since 1.4. While I can't speak to LISP's garbage collection implementations, Java has come a long ways in the past 16 years.

> Dynamic Typing. Java attaches run-time type information to instances of the class Object, but not to primitive data elements. However, Java requires a type declaration for every variable. This has some advantages for production code, but has disadvantages for rapid prototyping and evolution of programs. Java does not have a generic/template system that would allow types life Vector<String>, and suffers greatly because of it.

Specifically, Vector<String> has been available since Java 1.5. Java 10 brings local type inference ( https://developer.oracle.com/java/jdk-10-local-variable-type... ) so instead of

    Map<User, List<String>> userChannel = new HashMap<User, List<String>>();
in Java 1.7 one can write:

    Map<User, List<String>> userChannel = new HashMap<>();
and in Java 10:

    var userChannels = new HashMap<User, List<String>>();
> First-Class Functions. Java has anonymous classes, which serve some of the purposes of closures, although in a less versatile way with a more clumsy syntax. In Lisp, we can say (lambda (x) (f (g x))) where in Java we would have to say new UnaryFunction() { public Object execute(Object x) { return (Cast x).g().f(); } }

Lambdas are available in Java 8. https://docs.oracle.com/javase/tutorial/java/javaOO/lambdaex...

As a note, the bit about python closures being read only ("The only drawback is that closed-over variables are read-only.") gets to a very interesting debate on first class environments that is described at http://funcall.blogspot.com/2009/09/first-class-environments...

> Interactive Environment. Some Java environments allow Lisp-like features such as an intgeractive command loop and stop-and-fix debugging. Lisp environments are still ahead, but that probably won't last long. BlueJ in particular has most of the major features of a good Lisp environment: you can recompile a method into a running program, and you can type in an expression and have it evaluated immediately. It is intended for teaching purposes, and I can't tell if it is suitable for production use. Python has the same interactive approach as Lisp, but the environment is less mature than Lisp's.

Eclipse, Netbeans and IntelliJ all have rather advanced debuggers. Eclipse has a 'display' view as shown in https://www.ibm.com/developerworks/library/os...

Sorry, none of this comes remotely close to what Lisp provides.

Also the code you wrote won't pass production code review at any decent Java shop.

If you want you can do very functional stuff in Groovy. Here's an example of function composition:

   f = { it * 3 }
   g = { it + 4 }
   h = f << g
   h(2)
   18
The reason this is not particularly common usage is not because you can't do it, but because most people don't find it very intuitive.
The unintuitive part is the choice of operator `<<` instead of `>>`. in your example `f << g` means do `g` first (i.e. 2 + 4 = 6) then do `f` on the result (i.e. 6 * 3 = 18). I would have expected `f >> g` for this behavior.
It's following the semantics of wider use of `<<`` in groovy which denotes forwarding contents of one object into another, eg: adding to a list

    list << x
or writing to a stream:

    new File('test.txt') << 'hello world'
So if you read it as "forward the result of g into f" then it makes a little more sense.

But I agree, it's still not very intuitive and for readability I would normally just write:

    h = { f(g(it)) }
Which I think is far clearer. But that's partly an argument against functional programming style itself which would ordinarily see function composition as more idiomatic.
I really don't follow your expectation here. "Data moves in the direction of the arrows" seems natural to me. Can you unpack it at all? I'd like to understand where you're coming from.
None of those Apache Groovy examples you gave work when scripting Jenkins pipelines, because the all the functional methods (e.g. each, collect, findAll) are crippled so they don't work. Groovy is more like Bash for Java, rather than Perl.
> Java 5 was 2004 which brought with it a collections rework and a lot of improvements with not everything needing to be synchronized

Actually, the collections rework with not everything needing to be synchronized already came in Java 1.2 in 1998.

Thank you for refreshing my memory. I should have checked @since in the javadoc.

That said, the description in the article was for Vector as the list implementation.

The key problem with this article is, that Norvig cherry-picked features of Lisp that are present with the languages that he contrasts it with. So, what happens then is that it will give the impression that Lisp somehow lost its uniqueness, or whatever makes it stand out among the others. The premises were at best, loaded.

If we’re going to talk about CL, here are some of the features that still make it unique:

  - live update of a running program, including (re)definitions of classes, condition handlers, etc.
  - object system which has multimethods, multiclasses, multidispatch.
  - it has a debugger and stepper which has complete access to the stack, with unwind protection
  - it has a very strong, unhygienic macro system.
The average programmer does not need a lot of these things because:

  - the tasks do not demand those features
  - the programmer doesn’t know them
  - the programmer doesn’t want to or can’t invest time in them
The main problem I had when learning CL was its standard library that at felt both enormous and strangely deficient in utilities for day-to-day work. CLHS is fine but the definitions can be obtuse, navigation isn't all that easy, and it's just a generally daunting approach to learning a language. There's a big gap between the code you learn in Practical Common Lisp and what you see if you open the source for any popular CL library.

It's like Haskell, without the hype. The underlying paradigms aren't that terribly different, but the actual terminology for expressing ideas idiomatically is wildly different, and represents an unusual obstacle.

I wish every language had docs like the CLHS.
>The underlying paradigms aren't that terribly different, but the actual terminology for expressing ideas idiomatically is wildly different, and represents an unusual obstacle.

Haskell uses a lot of terminology that's familiar to mathematicians, but unfamiliar to everyone else.

CL uses a lot of strange terminology, because it incorporated concepts before their names were standardized and used in other languages. You're left spending a lot of time trying to figure out that a wheel is called a frob or you just end up re-inventing the wheel even though the frob was already in the standard.

Also, he writes 'Python's object model is the same as Lisp's' which makes me think either he doesn't understand object models (really, really unlikely) or that he & I understand the phrase 'object model' to indicate very different things (more likely).

I think 'object model' refers to e.g. how classes, instances & methods work together; Lisp & Python have radically different object models.

Maybe he uses it to mean something like 'type model'?

I suspect, that when he meant they are the same, he wants to convince the reader about his thoughts and sentiments about it. Norvig is a highly regarded CL author and programmer. Cognitive dissonance is such a powerful phenomenon.
Reading https://norvig.com/python-lisp.html this morning, it appears that my conjecture is correct: Norvig uses 'object model' to mean something like 'everything is an object,' 'objects have type, variables don't' & 'introspection of objects & classes is strong.'

In that sense, Python & Lisp are similar. But I don't think that's a good use of the phrase 'object model'; I think it's more usefully applied to the model objects follow.

The really radical difference between the Lisp & Python object models is that Lisp objects don't have methods. Where in Python one adds methods to a class, in Lisp one specialises generic functions on classes — and one isn't limited to specialising on a single argument. There's no clean way in Python to implement a method that works with a Foo and a Bar: one can either implement a method on a Foo that takes an argument of any type, or a method on a Bar which takes an argument of any type (although ISTR some rumours of typed Python; dunno how that would change things, but the methods would still live inside one class or another).

In Lisp, though, generic functions are their own things: they don't live inside a class at all. One would just specialise a generic function taking two arguments such that one is a foo & one a bar.

The difference is between this:

    class Foo:
        def baz(bar):
            # bar might or might not be an instance of Bar
    
    class Bar:
        pass
or this:

    class Foo:
        pass
    
    class Bar:
        def baz(foo):
            # foo might or might not be an instance of Foo
and this:

    (defclass foo () ())
    
    (defclass bar () ())
    
    (defmethod baz ((foo foo) (bar bar))
      ;; foo is always a foo & bar is always a bar
      )
This is so unlike Python as to be unrecognisable.

There's another difference, which used to drive me crazy in Python: when I redefine a class in Python, existing instances don't get updated, so if I had a REPL with a lot of state I'd have to manually convert the old instances to new ones. But with Lisp this happens automatically, and if one needs to run extra code (say, because the old & new class differ in an important way) then there's UPDATE-INSTANCE-FOR-REDEFINED-CLASS. That, BTW, is a generic function.

That's something I really like about Lisp: like SmallTalk, it's built for programming in the large, on stable systems which have to run for a long time. 'Just reboot it' is not a very Lispy idiom.

'object model' in Lisp has two traditional meanings. Originally data with identity is called an object. A cons cell, a vector, a string, some numbers, characters, symbols, ... all these are objects. One can reference any of them via variables, can return them from functions, give them as arguments to functions. Arguments are not copied. Function results are not copied. Etc. The JVM for example has a similar 'object model' like Lisp - thus the GLS quote: Java developers were half-way dragged to Lisp.

The second meaning is the object model in the sense of 'object oriented programming'. One of the similarities might be the use of meta-classes. Generally though CLOS isn't that near to the class-based OOP object-model of Python. CLOS uses a mixed model of classes and independent generic functions (with multiple dispatch)...

Smalltalk has live update of the running program, arguably in a much better way than (modern) Lisps. In Smalltalk you edit code directly in the running image, there's no possibility of defining a function in the REPL and having a different definition in a source file somewhere: the source is the image, and any change to code is reflected in the image. The old Lisp machine Lisps (like Interlisp) used to do this too, I think, but no CL implementation I know of does this anymore.

The Smalltalk debugger is famously also implemented in the development image (and implementing a simple debugger is a common project in Smalltalk textbooks). Traversing the stack, accessing locals, stepping, etc. are of course all possible.

No CLOS in Smalltalk admittedly, but lots of shenanigans are possible by fiddling with the object model.

Likewise no macros, but Smalltalk syntax is so minimalistic that creating control structures that look like the built-ins is trivial. In fact, conditionals, loops and the like are entirely unmagical in Smalltalk (modulo performance optimizations to make things go fast); reimplementing conditionals (that look and work exactly like the standard set) for example is trivial.

> the source is the image

Actually I thought the sources are stored in a source file and a corresponding changes file. True, one could de-compile the byte code - but usually the Smalltalk editor uses the sources from the sources/changes files.

Smalltalk actually tracks the editing operations / class operations and uses the sources/changes file as a simple code database.

> there's no possibility of defining a function in the REPL

If I programmatically create a class in Smalltalk, is it recorded in the source?

> Traversing the stack, accessing locals, stepping, etc. are of course all possible.

we do that in Lisp, too - the difference is that there is no virtual machine specified and none is common. The default mode of executing Lisp is a) interpreting or by using compiled code (usually to machine code, or to C and then to machine code, ...). There are virtual machines, but they are usually tied to a specific Lisp implementation.

The code is obviously stored somewhere, but that's not a user-facing place. Thus, if you define a function in the Smalltalk REPL equivalent (a workspace), when you open that function in the code editor later, you'll see the definition from the REPL, which is the interesting point. There's no way to have one definition on foo in foo.lisp and a different definition actually running in your image because you did a `(defun foo() ...)` from the REPL.

> If I programmatically create a class in Smalltalk, is it recorded in the source?

There's no distinction between programmatically created and other elements. Using the UI to create a class is just a front-end to the metaprogramming facilities you'd use to do it yourself at runtime.

> you'll see the definition from the REPL, which is the interesting point

whose source was put into the changes file

It's managed source code, but the source code is not in the image itself. Smalltalk tracks the connection and creates change records for changes done by meta-programming.

That's for example slightly different with a residential system like Interlisp-D / Medley, where the code is in the image itself, the editor is a structure editor actually editing it and the system can run either the source directly (via the Lisp Interpreter) or a compiled version of it.

But that method is not very popular in the Lisp world, where images are used, but changes are not tracked. Most Lisp development only track location of things. Changing sources is also not connected to quitting an image. It's a separate operation. Lisp also never bought into mostly a single development style/environment like Smalltalk did.

Other languages have copied features of Lisp, but they often have convoluted or bloated syntax. Lisp reminds me of "E=mc^2" and Newton's "F=ma": powerful ideas based on simple building blocks or formulas. These other languages may have mirrored Lisp's power, but NOT its simplicity.
My favourite thing about lisp is the lists and symbols, it is so nice for representing data because you don't have to declare data structures etc if that makes any sense.

No other language I know of that does that.

Rebol and Mathematica/Wolfram Language are two other that come to mind. Albeit Wolfram is kind of a Lisp. Possibly also Lua and its tables concept.
Erlang has lists and symbols. I do miss symbols when programming Python..
Yes, just like every other language.
As I see it, Lisp is a language in (pretty much) pure prefix notation and of which the core data structure is a linked list and in which functions are first-class values. The advantages of the prefix notational purity are manifold, but schools train people in the use of infix/Eulerian notation:

f(a+b,g(x,y)) versus (f (+ a b) (g x y))

The problem of notational convention has not been solved since 1958. Other languages painstakingly support infix/Euler, requiring comparatively incredible compiler construction effort. Doing so also removes the manifold advantages of Lisp's notational purity.

JSON is much more popular than S-expressions. Therefore, we can assume that nested arraylist/hashtables are more popular than linked lists as the core language data structure.

Pretty much every serious language treats functions as first-class values nowadays. So, this is no longer a strategic advantage in Lisp.

> incredible compiler construction effort

Not really. BYTE magazine, in the 1970s, published the complete source code for a Pascal compiler, including the code generator.

Pascal can undoubtedly be shoehorned into the target of a simple, single-pass LL(1) parser. Doing so comes at a real "flexibility" cost, though. For example, don't try to define your functions in any arbitrary order. It won't fly. As a programmer, you will surely notice the limitations of single-pass LL(1) compilers. Nowadays, infix/Eulerian notations are generally targeted with at least an LALR(1) parser. The notational complexity in languages like C++ (in gcc) and C# generally even exceed what LALR(1) can handle. Apparently, their parsers need to be painstakingly hand-coded. In my book, that is clearly overshoot. Do we really need to create a dependency on languages that even LALR(1) cannot handle? What are you even getting in exchange? Debatable ...
(comment deleted)
Those are all valid points, but not relevant to infix vs prefix notation.

I don't know about C#, but parsing C++ is complex because you need a symbol table to do it.

D doesn't need a symbol table to parse, but it does require arbitrary lookahead. It still isn't hard to parse, and the parser fits in one file:

https://github.com/dlang/dmd/blob/master/src/dmd/parse.d

I think you missed out symbols. That's a big part of Lisp programming and sorely missed in other languages.
But again, no longer unique to lisp - Ruby for example deeply integrates them as do Clojure, Elixir, and others.
Clojure is Lisp. In any case it's not just having one or two features from Lisp, every language has that, it's about the combination of those features.
"Other languages painstakingly support infix/Euler, requiring comparatively incredible compiler construction effort"

Shunting-yard is trivial. What painstaking, incredible compiler construction do you refer to?

Shunting-yard has an incredible validation problem. It works until you hit errors in the expression, and you need to output meaningful error messages. Even LL(1) reacts more gracefully to errors. I think that this is the main reason why Dijkstra's shunting-yard is not used in any serious scripting engine or compiler.
What are the top benefits to the programmer to this alternative notation? Generally readability issues don't come from the visual burden of separating keywords, names and data.
I had lunch with Peter around the time he updated this article. I had written two Lisp books for Springer-Verlag about 5-6 years before. I was still using Lisp but I think he had more or less switched to Python although he did write a nice Scheme system in Java around that time.

In 2018 I still use Common Lisp for a major personal project (hybrid AI, wrapping deep learning models so they can be used as functional components (just blogged about this yesterday http://markwatson.com/blog/2018/08/19/hybrid-artificial-inte...).

At the end of the day, language matters but libraries, tooling, and platform support matter more.

I've always been intellectually fascinated by Lisp, but would NOT want to use it in a production (team) environment. It has two problems:

First, is that it's too "meta" in that one can shape it to be just about anything they want. This can make it difficult for others to read your code, for they have to know your mind's style. Our typical "office" languages tend to hard-wire idioms, such as control structures (if, while, case, try, etc.) which make it easier for other readers to digest. It may be less parsimonious (more code), but it's overall easier for an outsider to come in and read it.

Second is that nested lists are the wrong base structure for humans. Our natural languages are closer to maps or ordered maps (AKA "associative arrays"), not nested lists. I was working with others at the C2.com wiki to form something similar for maps: a meta-heavy map-oriented language. We nicknamed the project "MASP". C2 fell apart, but I have some new ideas for MASP that I may write up one of these days.

MASP may never go mainstream because of its heavy meta-ing, but could be great for experimental, hobby, and rush-to-market startup projects. Paul Graham has said Lisp's meta ability gave him an edge in the "stores race". Being first-to-market matters in dot-com-ville more than team-friendly code.

> This can make it difficult for others to read your code

This is solved simply by making the program have suitable conventions. A good team does that anyway.

I'd actually argue that strict "maps" are the wrong choice. Instead, human learning is much more akin to an alist of data that you periodically garbage collect. Sometimes, having multiple "entries" for a single key makes a ton of sense and you essentially key key->value mappings based on recency and overall capacity of your entire experience, not experience in just a single mapping.

For your first point, you seem to be making an assertion with no data. I get the fear. I have never actually seen it play out. Worse, I have never seen it play out that these other languages are easy to just jump into. You can soften the claim to "easier" but you have not established a baseline. Just blindly claiming it would have been harder in lisp.

As I mentioned above, it probably varies per person. Nobody has reliable "head data" (studies) either way. If you can donate for a formal study, that would be great. But until then, my experience with other programmers is that people think different from each other. One's own head is not necessarily extrapolate-able to other heads. It's too small a sample size.

For my head and a couple of others who have agreed with me, an ordered map is a better root linguistic structure than nested lists. It's more natural to OUR heads. No language will make everyone happy, but map-oriented languages are under-explored. I intend to explore. Call me "Ferdinand Mapgellan".

I didn't say a nested list in this regard. I specifically think alists is more natural to me. Basically, give me the first meaning I can think of for "Dust". Is it applicable here? No? Go down the list to the next definition of Dust.

That said, I'm not trying to stonewall where you are searching. :) Good luck!

> This can make it difficult for others to read your code

Actually I found that one can write very nice code with macro heavy code. Reading the code is not the real problem - the problem is debugging/extending. There are some macros, which are difficult to use - but Lisp has all the mechanisms to write very clean code with excellent error handling.

> but it's overall easier for an outsider to come in and read it.

That's not different from a large layered object-oriented architecture - like in Java. There are enough stories of Java teams where the architect went fully generic and reimplemented all kinds of layers in his/her own style. That's no less difficult to understand than a layered language in Lisp - with the added advantage that the layered languages can be made much more problem specific and thus is easy to read - because the code is actually shorter.

> Second is that nested lists are the wrong base structure for humans.

That would be surprising, since people are used to work with lists.

Actually lists in Lisp are only a carrier for things. A property list or an assoc list is a map.

The really first object-systems were based on property lists hanging of from symbols:

    CL-USER 1 > (setf (symbol-plist 'hans)
                      '(:class        :computer
                        :machine-type :lispm
                        :vendor       :symbolics
                        :model        :3600
                        :memory-size  20
                        :site         (:city hamburg :project :hamans)))
    (:CLASS :COMPUTER :MACHINE-TYPE :LISPM
     :VENDOR :SYMBOLICS :MODEL :|3600|
     :MEMORY-SIZE 20 :SITE (:CITY HAMBURG :PROJECT :HAMANS))

    CL-USER 2 > (get 'hans :model)
    :|3600|
(comment deleted)
Re: people are used to work with lists.

To a degree, yes. But it's not the best root linguistic structure, by my observation. But I do realize it probably varies from person to person. Others have agreed with my map observation more or less, and thus there is a potential audience for it, perhaps even bigger than nested lists.

And I do realize one can emulate maps in Lisp, but it's not native, and ends up getting mixed in with lists unless somebody with a big stick stops it. I'm working on a "native" map language so sticks are not necessary. (Similarly, MASP drafts can emulate lists, it's not just as easy or natural.)

> root linguistic structure

The root linguistic structure of Lisp are lists of symbols:

   (move ship harbour)

   (move ship :location harbour)

   (collide ship asteroid :with-speed 100)

   (differentiate '(x * 2 + 3) 'x)

   (loop from i below 100 when (evenp i) collect i)
> even bigger than nested lists

since lists have been used in Lisp for map-like data a lot, there is very litte advantage on a 'linguistic' level.

   (loop for (name age) in '((fred 10) (jane 21) (alfred 9)) ...)
It makes not much different if the data map uses a different character. A Lisp user would probably layout the data so that it is easy recognizable as a map/table. A typical Lisp user is trained to read structure and not so much parentheses. It's a bit like bicycle riding - it looks difficult if you haven't learned it already.

  ((fred  10)
   (jane  20)
   (alfred 9))
What Lisp already makes usual to many is that it uses a data-structure - s-expressions - to encode programs in.

> but it's not native

Lisp-based maps were in Lisp since basically day one and were widely used. There was little demand for different maps for a long time. There is little added benefit from having 'maps' native. Lisp developers instead added flexible object systems, where the instances were map-like. There are really a zillion of them in all kinds of shades. Hash-tables were added to Lisp probably in the 70s not as a new notation, but as an efficient data structure for large amounts of key/value data - for example to implement the symbol table.

There is even an example in CLTL2 how they looked like in Connection Machine Lisp:

  {moe->"Oh, a wise guy, eh?"
   larry->"Hey, what's the idea?" 
   curly->"Nyuk, nyuk, nyuk!"}
https://www.cs.cmu.edu/Groups/AI/util/html/cltl/clm/node192....

There are many other examples of these, but generally people were working towards objects as maps, with types and inheritance, etc.

Personally I don't think anonymous maps scale well for large interactive applications I'm interested in. I prefer to see at runtime, what type this map is about (is it price data, is it a product list, ...) - usually in form of an object-system, a frame system, or similar. If I don't need that and want to write literal data, I can just as well use a list (assoc list, ...). But then I've seen a lot of Lisp code and are used to it.

You ignored my "stick" point; it still stands. Anyhow, I'd like clarification on "doesn't scale well". (Large apps should probably be partitioned into smaller apps that use a database or the like to share info/state.)

And I think we have a different concept of "native". But anyhow, I don't want to continue a "language war": people like what they like.

Common Lisp doesn't have first-class maps (although lispm is right that you can think of lists as a thin abstraction around all kinds of data structures). But Clojure does have first class maps complete with their own syntax:

   {:key1 "Value 1"
    :key2 "Value 2"}
as well as sets, and a few other native types.
In libraries of CL there are many types of maps. List-based, hash-table based, object-based, ...

In Lisp, the list-based map is 6 decades old:

   (:key1 "Value 1"
    :key2 "Value 2")
Lisp is largely obsolete. Clojure is the truly modern Lisp. It integrates seamleasly with the world's most widely used runtime, its data structures are immutable and persistent by default, vectors and maps are first-class objects alongside lists, and its list data structure is really a tree of vectors. Every C++ developer knows that you should always prefer vectors to linked lists because of how poorly linked lists perform under modern CPU caching hierarchies. Clojure lists are much more suited to modern CPUs than traditional Lisp lists are. Clojure also provides the abstraction of the "conjable", which allows transducers to be abstracted over many different collection types.
> poorly linked lists perform under modern CPU caching hierarchies

It's poor practice to have all data structures being dictated by machine architectures.

Actually there is very little evidence that a purely vector-based Lisp would be faster. From what I've seen, Clojure programs are not faster than CL programs. Maybe you have numbers which prove otherwise?

> Clojure lists are much more suited to modern CPUs than traditional Lisp lists are

'much more'? Evidence?