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

It's the lists.

No, not the prefix notation, parenthesis, what have you, although that doesn't help. The lists themselves. In Lisp, code is data, and data is lists.

Yes, of course, there are hashmaps, arrays, strings. But idiomatic Lisp code really does use linked lists extensively, it's an entire style of programming. Even if you'd prefer to use different data structures (and again, Common Lisp does support this), you still have to be able to read and reason about the many parts of the language designed for, as the old quip had it, prothething lithts.

The "weird looking syntax" does not help, but Lisp hackers are right that you get used to that part, might even appreciate it if macros are something you really care about. Structural editing a la paredit and parinfer are pretty nice too.

But when it comes with a weird way of programming, that's a bridge too far for a lot of people. It's harder to learn, read, and reason about, for longer than most languages.

I'm glad they mentioned Julia though. Learned everything Dylan had to teach and then some, real treat of a language, and, it turns out, writing macros without cons cells is rather pleasant and powerful. Certainly an acceptable Lisp in my book. Maybe someday they'll add conditions and restarts, that's the one feature CL which gives it an edge on Julia.



I didn't see mentioned in the thread (maybe missed it) the #1 reason, IMO, that Lisp can't be popular in companies.

Everyone should take the time to learn Lisp and do a handful of personal projects with it. It'll help your growth as a software engineer. Just do it!

But that doesn't make it a great corporate language. Lips is infinitely flexible, you can mutate it to be what you want. That's cool and feels awesome.

Also: a maintenance nightmare as soon as you have more than ~1 person working on the codebase!

Everyone surely has heard the joke/truism how C++ can be great as long as you only use a sane subset. But every team uses a different subset. Now imagine something like Lisp where every developer & team morphs it in a different way and you have a product with hundreds of developers on it. Try maintaining that without going insane.

The anti-Lisp is something like Go. Simple, not very flexible, everyone does it the same way mostly, you can plug & play developers like scrum demands we do.


> Also: a maintenance nightmare as soon as you have more than ~1 person working on the codebase!

Lisp isn't any harder to maintain than any other language. The Lisp codebases I've worked on, even professionally, were originally written by talented, experienced engineers and were in fact wonderful to maintain.

> Now imagine something like Lisp where every developer & team morphs it in a different way and you have a product with hundreds of developers on it.

Dr. Ian Malcom's could/should distinction applies here. Most Lisp teams do not "morph the language" willy-nilly. They set standards of what to do and what not to do, establish a house style, build up a library of in-house functions and macros, and the more junior programmers imitate the more senior ones, just like any other dev team in any other language. And the vast majority of Lisp teams are small, even if they are working on large applications.

"We wanted to make it possible for very large projects to be done by small teams of people and for smaller ones to be done by one person." --Tom Diaz, Director of Software Products, Symbolics, 1986: https://www.youtube.com/watch?v=-K01FQ73xgY&t=144s

> The anti-Lisp is something like Go. Simple, not very flexible, everyone does it the same way mostly, you can plug & play developers like scrum demands we do.

I think you might be on to something. Symbolics was very much in danger of making large software projects possible by one person or a small team of people. It seems as if the corporate world has responded to the proliferation of more powerful software development tools -- not only in Lisp, but certainly Lisp and Smalltalk had an outsized influence -- by lowering the skill ceiling to make devs more fungible, and creating more and more process to hobble their productivity so as to justify larger teams of devs and dev-adjacent personnel: PMs, POs, scrum masters, etc.


> Symbolics was very much in danger of making large software projects possible by one person or a small team of people.

Just that there were very few of these people. A typical saying: "there were more Lisp Machines (~ 10000 were made in one decade, over all Lispm companies) than programmers for them."


> The Lisp codebases I've worked on, even professionally, were originally written by talented, experienced engineers

But companies have untalented junior engineers, you need a language they can write while still remaining structured.

Talented professional engineers can write assembly language in a very easy to read and structured manner. But there is a reason basically everyone writes in structured languages today instead of unstructured, that enforced structure helps make code more readable and uniform.


With good leadership, untalented junior engineers can become very productive junior engineers. I've seen it happen. Within weeks some kids fresh out of college, who had been taught everything mainly in Java (this was still the JavaSchool era, more or less) were proficiently and happily contributing to our C++ code base. They were pretty good, but they weren't Carmack or anything.

Now if your leaders are untalented, you have an organizational issue: you're promoting schlubs. Why are you doing that? Stop doing that.


> I think you might be on to something. Symbolics was very much in danger of making large software projects possible by one person or a small team of people. It seems as if the corporate world has responded to the proliferation of more powerful software development tools -- not only in Lisp, but certainly Lisp and Smalltalk had an outsized influence -- by lowering the skill ceiling to make devs more fungible, and creating more and more process to hobble their productivity so as to justify larger teams of devs and dev-adjacent personnel: PMs, POs, scrum masters, etc.

This is a ridiculous LISP conspiracy theory that really needs to die. What world are you living in that corporations actively want their employees to work slowly or to have too many employees? Who does that benefit?

If lisp were genuinely powerful, corporations would be able to make bigger projects faster.


> What world are you living in that corporations actively want their employees to work slowly or to have too many employees?

Many companies are risk-averse and prefer predictability to performance. They therefore often choose slow and outdated software products instead of newer, more efficient ones and they prefer using tools that require large amounts of developers, but in a predictable fashion. Using a tool that allows a very small team to perform the job of 10x as many is risky because the bus factor becomes that much higher, as well as the difficulty in finding and training replacements.


> What world are you living in that corporations actively want their employees to work slowly or to have too many employees? Who does that benefit?

1) In the corporate world, a larger headcount under you means you get more funding for your initiatives. If you get things done with a team of five, that signals to upper management that that's all you need and your budget will be adjusted down accordingly.

2) When it comes to software, companies favor the ability to monitor and control the development process from above over speed. Agile only gets uptake among large orgs inasmuch as Agile consultants promise them this.

3) There is, decades after The Mythical Man-Month, a sense that you can get a good programmer's worth of output -- without a good programmer's worth of risk -- out of several, much more fungible, mid programmers.

4) Egos on the line. Tom Smykowski from Office Space is real. Need proof? Propose something sensible like having engineers talk to, or shadow, customers so they can see what the pain points are in their work and which pieces of the software need attention... and see who bristles.

No conspiracy is needed. Companies can remain stubbornly dysfunctional, and will bizarrely spend more to protect that dysfunction, for decades, than to try something better. My father proposed more resilient ways of running an assembly line in the 1960s, upper management laughed. In the 1980s what he proposed was one of the tenets of the "Toyota Way", but it took until the 90s/2000s for American managers to start listening.

This goes double when you realize that software is off most companies' critical path, and addressing performance issues among software teams is way less important than addressing performance issues among, say, sales and marketing teams.


Your comments are describing two different things.

- Technical leaders choosing a language that's easy for developers to Grok to make them more fungible, and make onboarding easier. - Corporate managers purposefully choosing a language as a "Response" to Common Lisp, as you said, to tank the performance of a company for more funding.

One is actually happening, the other requires me to believe corporations even know what Common Lisp is, which would be amazing. You're lucky if management even knows what Python is.

The conspiracy theory I'm referencing is the fact that people think Corporations just hate developer proficiency and will actively fight against tools that make people more proficient. That is different from someone coming to a manager with, from the managers perspective, an idea that could increase efficiency but make them look bad. The first is not something a manager actively does, but the second is an insecure reaction to someone "Rocking the boat."

Common Lisp did not take the world over for lots of reasons beyond hand wringing PMs.


You're missing the point.

It's not really a response to Lisp as such, it's a response to anything which makes developers more productive. Developments from the Lisp world have had trickle-down effects that make other languages and IDEs more powerful: garbage collection, hot-reloading, etc. The productivity gains realized by these developments, however, have been more than neutralized by a concomitant increase in corporate BS. So it's not really a conspiracy of orgs out to get Lisp but it does make Lisp a bad fit for organizations which rely on inertia, ego, and fungible worker-units, which is most of them.


I think the key point was that maintaining control over the employees by making them replaceable has more benefit that better products. That's what market capture is for


> This is a ridiculous LISP conspiracy theory that really needs to die.

Not really. Lisp can be a power amplifier if you can keep it under control. But, as I've argued above, that limits you to a small stable team.

If I could start a new company with a few solid Lisp buddies and I knew that's going to be the core team for the next decade, I think I'd use Lisp and it would be a significant productivity gain.

But but but.. those are some unrealistic constraints. If I'm starting a company presumably I want the company to keep growing as much as possible, not stay with a few core people for years. So no, I wouldn't use Lisp.

> to have too many employees

All of them, the goal of both startups and public companies is to continuously grow. As soon as growth stops that is seen as a problem and the decline begins. The only exception is privately held companies who don't run on VC money and don't have any intention to become public. Those might be the best candidates for Lisp.

> to work slowly

No company specifically wants that, but they vastly prioritize being able to hire 10,000 scrum interchangeable cogs to do development and that's not the environment where Lisp works well.


It's possible to build teams in the low hundreds. Example:

https://www.youtube.com/watch?v=hMVZLo1Ub7M

Siscog, company is 37 years old, 130 employees, 1.7 Million lines of Lisp code. In the planning/scheduling domain for railway companies.

Other example ITA Software, bought by Google for $700 Million dollar. 100+ Lisp developers.


> Siscog

This is really cool! Had not heard about them. Thanks!

Nice, so looks like low hundreds of developers may be possible given the right environment.

That said, I don't feel it disproves my impression. If the company is 37 years old, still privately held and still has less than a hundred developers then it is clearly not a company looking to grow fast above all else. So feels like a nurturing environment for Lisp.


> looking to grow fast above all else

They may also not need to. They may have enough people working in a productive environment. If you add a Lisp feature to a large code base, it may not take a lot of time to do so, given the possibility of an incremental development style with Lisp (-> changing running applications incrementally).


Lisp's primordial sin is the fact that it is FP. US cooperations never supported FP (see Microsoft and F#). FP is largely a European phenomenon.


Lisp is non-opinionated and supports FP, OOP, AOP, in whatever mix you want.


In companies, most languages have some kind of system to enforce style guidelines and restrict which dependencies can be used. Companies already restrict the flexibility of the languages they are already using. So flexibility is not the reason Lisp is unpopular among companies.


> some kind of system to enforce style guidelines

Are you talking about autoformatters like gofmt and its ilk? That won't enforce a consistent approach in Lisp which allows the programmer to infinitely outsmart any tool.

Or if you're talking about written-down guidelines, that can help, for a while. But those morph over time too, and so does the new code but the old code lives on forever. Every time a new CTO/Chief Architect shows up, things change but the existing code doesn't. Since we're on the topic of large companies with decades-old codebases, things have changed many many times and you'll have a very inconsistent mess in your hands. It happens even in fairly rigid languages like Java, I can't imagine what you'd end up with Lisp.

Has there ever even been any Lisp-based company with a 20+ year old codebase where a cast of tens of thousands of developers have worked on it over the years? I can't think of any but maybe I haven't heard of it.

Lisp is awesome for a team of 1 (for about a decade I used to write all my personal use code in Lisp) or maybe a small tighly-knit group. Beyond that, I can't see it working well over the long haul.


I definitely include linters, not just style formatters.

> It happens even in fairly rigid languages like Java, I can't imagine what you'd end up with Lisp.

I imagine it would be about the same, with human factors dominating. If Lisp's flexibility pushed in the direction of inconsistency, the ease with which you could write codemods for Lisp would push in the direction of consistency.

> Has there ever even been any Lisp-based company with a 20+ year old codebase where a cast of tens of thousands of developers have worked on it over the years? I can't think of any but maybe I haven't heard of it.

I haven't heard of any either. If you hear about one, LMK if they're hiring.


While ITA no longer exists as a separate entity I don’t think, my understanding is that they pretty much wrote everything in Lisp.

https://en.wikipedia.org/wiki/ITA_Software

https://franz.com/success/customer_apps/data_mining/itastory...

Grammarly too, although I don’t think it qualifies as a 20-year codebase: https://www.grammarly.com/blog/engineering/running-lisp-in-p...


ITA started doing everything in Lisp, but especially as the original coding founder left, started hiring people doing stuff in Perl, Python, Ruby, Java, shell, C++, PL/SQL, etc.

The core software was still Common Lisp, but lots of cruft got added, and Conway's Law kicked in mightily.


And what's the point of having Lisp if you can't use it? Unpopular opinion alert: Bigger companies want programmers to be replacible resources which means they need to have huge talent pools. If you choose your tech stack for the fact that you can retain your people for only 12 to 18 months, you can't effectively use any of the advantages Lips languages offer to you.


> Simple, not very flexible, everyone does it the same way mostly, you can plug & play developers like scrum demands we do.

I think that's why Python beat Ruby. :p


Agreed. And why I love Ruby and hate Python. But yes.


Ruby is "perl-esque" in syntax, and of course TIMTOWTDI is opposite to the 13th item of the Zen of Python, so it's understandable some dislike to an express design decision.

But hate? Really?


I personally really do hate python.


> But hate? Really?

Coding with Python makes me feel like I'm hand-washing dishes that have been dirtied and left in the sink for several days.

I know that many like Python, and I'm not arguing against that in any way. Whatever floats your boat, mang.

However, trying to tell me why I "ought to like" Python is like telling me why I "ought to like" cilantro. I know you think it's delicious. To me it smells like squashed bugs, and tastes like dish soap.


The dish soap thing is genetic if you didn’t know. The majority of the world doesn’t taste the dish soap.


> But hate? Really?

Yes; well maybe hate it a strong word reserved for life and death matters, but I intensely dislike python. It's like nails on a chalkboard reaction for me. It drives me crazy that it has become popular.


Wow. I feel that way with C# and Java, but mostly without any reasons I can rationally articulate. Meanwhile, I love Python!


> The anti-Lisp is something like Go. Simple, not very flexible, everyone does it the same way mostly, you can plug & play developers like scrum demands we do.

This has not been my experience with Go. The only areas where Go actually enforces consistency is that gofmt has no configuration surface, and Go Modules conclusively won the dependency management war. Other than that, it's still a lawless wasteland.

Foreword: Go is not bad, far from it. It does a lot of things right and you can do a lot worse. But myths around its simplicity, idioms, best practices, etc. have created a generation of monstrous tech debt hiding behind tidy syntax.

One of the worst myths is that it's easy to see the right way to do something in Go, because now when everyone does things their own batshit ways, they each think they did it the obviously right way.

* Go has no build system, and you'll need one for anything but the most trivial projects. Many use `make`, but some people insist that `just`, `ninja`, etc. are worth people having to install extra dependencies. The worst is when projects use straight shell scripts, having all of the platform compatibility problems of make with none of the benefits.

* Go has no macros. Do you use reflection, code generation, or write everything out by hand. You can't even parse some JSON without being forced to make a choice here. (The standard library option, `encoding/json`, is the worst by far. It's also the most popular because of course it is.)

* Even when you're done with that, you still have to choose a way to validate that input, because e.g. Go doesn't even have a concept of a value being "required" so you'll be introducing that somehow. The "validation" frameworks (sigh) that have an opinion on this still do it inconsistently for different builtin types, and often silently do nothing for custom types. Good luck being consistent even within a project let alone between projects and teams.

* Go generics are very limited, and how you work around those limitations can vary greatly, e.g. do you use receiverless methods to simulate associated constants/functions or do you go back to reflection for those. There's still no solution for associated types, you either make do without them or abandon the type system altogether and use reflection. (In a LISP thread it might be hard to imagine just how bad this can get, because there's dynamically typed in a language that's built for it, and there's dynamically typed in a language pretending to be statically typed)

* Channels, mutexes, and atomics all have their place, but most people never understand their tradeoffs properly (and community myths do more harm than good), so not only do they architecture projects around different options, but often the wrong options for their requirements. People will make performance arguments with no measurements, and correctness arguments with no proofs, etc. and when you join an existing project you'll be sifting through its uniquely crafted wreckage to figure out what invariants can even begin to hold.

* The Go community has a lot of vitriol against testing frameworks, but Go has no useful functions for comparing values of non-trivial types, so you either use a test framework after all or reinvent one badly. You can't even define equality in Go recursively through pointer, slice, or map types -- strings compare by contents, pointers compare by address which is almost never useful, and maps and slices cannot be compared at all. It's not even consistent among builtin types. It's very common to use reflection here, see above, this is its own special hell.

* Go has no sum types, pattern matching, or enums. (No, multiple value returns are not enums, they are not a type you can use as a map key, they cannot nest, etc). You'll be reinventing these somehow with structs and interfaces and switches which have to choose to panic or ignore unexpected branches. Many projects, knowingly or not, create unenforceable sum types where different records use different subsets of fields of a struct. It makes C look modern and high-level, because at least C has unions.

I could go on. Go gets far more credit here than it deserves. Again, it does a lot right and it can be used well, but you should not expect quality or consistency from a broad range of programmers just because it's Go. Management that banks heavily on this myth generally pays for it dearly.


> Go gets far more credit here than it deserves.

When I said it is kind of the anti-Lisp, I wasn't thinking of it as credit...


I needed this. Thank you.


> But idiomatic Lisp code really does use linked lists extensively,

Idiomatic python code uses lists extensively. In my (somewhat limited) experience, they're the default data structure to use for a lot of algorithms.

Slightly more accurately, a lot of stuff in python uses sequences extensively, which are implemented by a number of types - but the default sequence is a list. And strings are lists. Yeah, if a list doesn't cut it then try sets or tuples or generators or coroutines or something else that implements a sequence. But for your initial prototype? List.

And python's pretty popular.


A Python list is an ArrayList/vector, not a linked list. CL lists are linked lists of cons cells.


Correct. There's no such thing as a python a-list, or p-list, and they can't share structure. There's overlap between programming in Python with lists, and programming in Lisp with lists, but not as much as it appears.

In idiomatic Lisp, it's rather common to search a list for a value, cdr it, and cons that cdr to the collection you're building up. Python has nothing like that, because what I said makes no sense in terms of Python lists.


What do a-list/p-list have to do with sharing structure?

If you want shared structure of lists in Python, use `itertools.chain`.

If you want key-value mappings, use a dict. Order is preserved by default nowadays, and conversion to/from an iterable of 2-tuples is trivial.


> If you want shared structure of lists in Python, use `itertools.chain`.

That would involve zero shared structure. itertools.chain is just an iterator that iterates one iterable and then, instead of declaring that it's done, goes on to iterate another one.

Shared structure between lists means that two lists refer to some of the same regions of memory.


And multiple `itertools.chain` instances pointing to the same list chunk(s) also use sharing.


OK, that is true, but that approach would have the significant downside that you can't pass over a list more than once.


Iterators can be shared in Python e.g., here's a well-known grouper recipe (traverse `it` iterator in chunks of `N` items each):

     zip(*[it]*N)
Example:

    >>> L = [1, 2, 3, 4]
    >>> *zip(*[iter(L)]*2),
    ((1, 2), (3, 4))
Here's the single result of the iter() call is shared. The purpose is to show that iterator can be shared in principle (ignore other aspects of the code).


Does the CL spec require the implementation to use linked lists or merely to behave as if it were a linked list? I believe it is the latter and that makes a world of difference when it comes to optimization.

A great mainstream example of this are JavaScript Arrays. They are defined to behave as hashtables. They inherit from Object. Their "indices" are actually strings instead of numbers (all object keys are strings and numbers get converted to strings before the lookup happens).

Despite that spec model, JS arrays have used a very different representation in practice for decades. Even in the dark ages, arrays were implemented as linked lists. Today, they might be hashtables, linked lists, boxed objects, or even unboxed primitives all while still pretending to be hashtables.

Cons doesn't have to return a linked list element.


> Does the CL spec require the implementation to use linked lists or merely to behave as if it were a linked list?

"list: a chain of conses in which the car of each cons is an element of the list, and the cdr of each cons is either the next link in the chain or a terminating atom." -- https://www.lispworks.com/documentation/HyperSpec/Body/26_gl...

Interpret that as you will. But what "world of difference" optimizations are you suggesting here? Give an example of a significant optimization at the implementation level that preserves cons semantics.


People confuse code at the exploratory phase with final production code.

Linked lists are a massively flexible data structure which can be great when you are still feeling your way around a problem domain.

Once you have a good sense for the shape of your data it is relatively trivial to go back and update the codebase with performance optimized structures.


I've felt my way around a new problem domains many times, and my experience is that a vector and a map will do just fine.


That may or may not be the case (that they're great for exploratory code, I have actually mixed feelings about that), but it in fact points directly at the problem I was referring to: using Common Lisp in the idiomatic and expected way involves reading and writing a whole bunch of list processing code.

It's not so much that it's a foreign language in the syntactic sense (although it is) but the idiomatic semantics are also pretty foreign to the modern developer. That's a barrier whether or not the promised land is on the far side of the hill.


How is the idiom of linked lists foreign to modern devs?

You create a list of items. You can add/remove elements to the start, end, and even the middle and even indexing is just as easy with the `nth` function.

From a programmer perspective linked lists are universally better. That is to say that if a computer could perform operations on a linked list as quickly and with as little memory as it could for an array, nobody would ever choose to use arrays. We only use arrays because the computer forces us to.


> if a computer could perform operations on a linked list as quickly and with as little memory as it could for an array, nobody would ever choose to use arrays

You can start from a false premise, and draw any conclusion you like. If a computer could perform operations on a linked list as quickly and with as little memory as it could for an array, purple unicorns would be grazing on my lawn right now. :)


My assertion is that linked lists map BETTER to the human and WORSE to the computer. The thought experiment is simply a way to prove that fact and is not a premise let alone a false one.

If arrays and linked lists had the same performance characteristics, we'd pick linked lists every time because they map better to how humans think about problems while arrays map better to how computers think about problems.

Thus we can conclude the exact opposite of the assertion I was responding to. People naturally gravitate toward linked lists then get forcibly re-educated to use arrays because that's what makes the computer happy.


My assertion is that linked lists map BETTER to the human

That's your assertion, but you didn't back it up by anything other than saying you can insert into the middle of a list (which is rare to need and can be done with a sorted map).

Thus we can conclude the exact opposite of the assertion I was responding to

You can't conclude anything from someone making the same assertion with no evidence over and over.

Pragmatically what people use over and over are arrays and hash maps. There's a reason perl, python, lua and javascript all thrived by having arrays and hash maps built in to the core of the language.

If you want to loop through something or index by an offset you use an array. If you want to access by key, you use a hash map. In the rare scenario you need to insert into the middle of a specific order, use a sorted map. Modern programming has no place for basic linked lists of granular data and they aren't missed in any sort of real scenario.

If can back up what you're saying with any sort of evidence or a specific scenario, go ahead.


Linked lists in their basic form are essentially obsolete. Having small items linked by pointers on a modern computer means following the pointer, allocation and deallocation are all more expensive than using data.

This sounds like a backwards rationalization without a technical explanation. Why would a linked list be any better for 'exploratory' programming? The vast majority of data structures are arrays, hash maps and occasionally a sorted map for the times where you need to insert something in a specific place.

What scenario is a linked list valuable in 'exploratory' programming where it wouldn't make more sense to use a different basic data structure?


I love Python's list comprehensions. When I first discovered them I had a kind of "mind blown" moment. They look like this, for anyone who doesn't know:

  squared_div_by_3 = [i**2 for i in range(10) if i % 3 == 0]
Without using a list comprehension, this is equivalent to:

  squared_div_by_3 = []
  for i in range(10):
      if i % 3 == 0:
          squared_div_by_3.append(i**2)


Of course, Python would have been way more awesome if they did not reject the much more reader-friendly and idiomatic:

  squared_div_by_3 = [
      for i in range(10):
          if i % 3 == 0:
              i**2
  ]
That is, just transforming the normal for loop and if into a list comprehension by surrounding it with brackets. It would even preserve the friendly forced blocking and indentation. Can you imagine?


It's inspired by set-builder notation: https://en.wikipedia.org/wiki/Set-builder_notation


Nim has a `collect` macro which works exactly like this.


Reads less like Perl in Lisp:

    (loop for i below 10 when (zerop (mod i 3)) collect (* i i))


With iterate, for comparison:

  (iter (for i below 10) (if (zerop (mod i 3)) (collect (* i i))))


Are you joking?


There seems to have been a design emphasis in C# on providing something similar under the name LINQ. It would look like this:

    var squared_div_by_3 = from i in Enumerable.Range(0,10) where i % 3 == 0 select i*i;
or alternatively:

    var squared_div_by_3 = Enumerable.Range(0,10).Where(i => i % 3 == 0).Select(i => i*i);
In that second syntax, `Where` is exactly equivalent to Python's `filter` (or lisp's `remove-if-not`) and `Select` is of course equivalent to `map`; I assume the odd choice of names for C# is meant to appear similar to SQL.

They're introducing Range objects with syntactic sugar so you can say `0..10` instead of `Enumerable.Range(0,10)`, and you'd think there would be LINQ implementations for those objects, but there aren't.


In Haskell:

let xs = [x*x | x <- [0..], x `mod` 3 == 0]

You can decide later how many you actually want.


In racket:

    (for/list ([i (in-range 10)
               #:when (zero? (remainder i 3))])
       (pow i 2))
there’s also for/vector for/and for/or for/first for/last, most likely for/set. Hashes. can add your own, etc…


I've not used python much but is that not the same/similar to the unfold function in functional languages?


I like python significantly more than lisp.

There are so many ways to express yourself in python that are troublesome in lisp.

I actually think list manipulation is easier in python than lisp.

I don't know, is there a lisp dialect that makes common data structures available in a multitude of ways?

I seem to be able to manipulate lists quite easily in python, and switch back and forth to sets or hashes.

but in lisp you have to dig in to find (for example):

- how to prepend to a list

- how to append to a list

- how to remove an element from a lisp

- how to do slices of lists

it seems to me like a lot of operations in lisp are needlessly efficient too.

Like instead of copying a list, you have to modify the list in place.

if my list will only have 10 elements, and my machine does millions of instructions per second, can't I copy it around a few times if I want?


> is there a lisp dialect that makes common data structures available in a multitude of ways?

Clojure. The language is built around immutable data structures with the expected interface for maps and vectors, and the idiomatic list-churning we're both referring to is unified through the sequence abstraction. So switching from a vector to a map can often be as simple as switching constructors. Because they're immutable, every operation makes a "copy" but does so efficiently.

I don't have a lot of use for Clojure these days but I enjoyed working with it. Rich Hickey is a smart fella.


this is a controversial take, but IMHO, the immutable data structures make Clojurescript (+ Reagent) a very, very nice way to build SPAs.


I don’t think you know Common Lisp very well, or are talking about some super limited Lisp dialect I’ve never heard of.


99% emacs lisp

hmm... maybe I need to look at the packages that use common lisp.


You only have to dig because you are less familiar with the language.

> copy list

Almost all functions have a copying and destructive variant.


This is all "I don't know the language" type stuff.

If you don't know Python, slicing syntax is also strange.

Common Lisp was VERY forward-thinking and has all kinds of these features that only became mainstream decades later.



Look into Hylang.


> And strings are lists

Eh, not really. Lists are mutable, strings are not.

Strings are iterable and sliceable, which makes them usable in many similar ways to lists, but they're not lists.


Afaik, in Clojure lists are immutable by default, and strings (not literal ones) are mutable in Common Lisp. So I'd say depends on the flavour of your Lisp.


> It's the lists.

That is one of the defining differences between Clojure and older Lisps. Clojure has list, vectors, maps, and sets as equally well supported data structures in the syntax and standard library. Classical Lisps often miss proper abstractions over different data structures. Clojure has those as well.


I think it's strange that anyone would even ask why common lisp isn't popular. There is no payoff to writing something in it. The binaries are huge, it is going to be a lot less clear than modern C++, it won't be as fast, you still have a garbage collector, the libraries are niche, the syntax is reversed, the tools are niche, the ecosystem is niche, and everything you do is the opposite of what is intuitive at first.

Then on top of all this is built on linked lists which are essentially an obsolete data structure in their simplest form.

There is no reason to learn something with backwards syntax and ancient tools when there isn't even any payoff. Write something in C and the program is fast, small, native and can be compiled anywhere in a fraction of a second. There is still a payoff for all the very real pain. In lisp there is just no reason to use it from any angle other than how clever someone can be with macros and that is the exact opposite of good sustainable programming.


I don't think it's the lists. I think it's the cons cells. Lists are pretty ergonomic in most languages. As someone who loves Lisp (Scheme, specifically), cons cells are elegant for the compiler nerds and a complete nightmare for the programmer. Fuck `car`, `cdr`, `cdar`, `cddar`, `caar`, etc.. It's a horrible interface and every time I need to remember which variant gets me the value stored at the tail of a list I get a little more angry and want to write more Clojure and less Scheme.


I disagree. I have no problem with lists obsession in other languages like OCaml.

The parentheses are just too much. I get how elegant and unambiguous they are for computers but I am not a computer. It's like RPN. It's elegant and easy for code to parse and unambiguous and all these nice things.... except it isn't easy for me to parse.

Compilers are perfectly capable of compiling readable code like Rust so I don't see why I should have to do the tedious work of figuring out all the parentheses manually.

Lisp is like a really great IR. But I don't want to program in an IR.


> I don't see why I should have to do the tedious work of figuring out all the parentheses manually.

I think most people who write a lot of Lisp don't do that. I use Paredit mode in Emacs, which doesn't allow the parens to become mismatched and has operations like "move the last token out of this expression" and "jump to the next expression" so it actually feels like you're editing a tree rather than a chunk of text.

A quick search says there's an equivalent for VSCode, and I'm sure other editors have options as well. I've seen some amount of structural editing for languages that don't use s-expressions, but it's always pretty limited.

If you've written Lisp with a good structural editor and still don't like editing a program as a tree, then we think about code very differently.


> operations like "move the last token out of this expression" and "jump to the next expression"

And that is the problem. When I am writing code, last thing I want to do is to think about tree manipulation and the numerous commands to do similar-but-not-the-same operations.

This is the same reason I don't like Vim, it's hard enough to keep the problem domain in mind, there's no mental space to also remember the ed-style editing commands.


This must be where we think about code very differently.

Most code has a tree structure, though many put the root token outside the brackets that mark it. Languages like C, Java and Rust use brackets to express their tree structure much like Lisp does, though they use more flavors of bracket than Common Lisp. Python uses indentation, but it's still expressing a tree. Ruby, Lua, BASIC, and others add keywords, but the stuff inside `do ... end` is still a branch of a tree.

Without a structural editor, I'm still building a tree, but I have to keep more of it in my head.


After the learning period it becomes muscle memory and you don't have to think about it. If you're not willing to put in even the small amount of effort to build up new muscle memory, I don't know what to say other than don't be so defeatist.


I think it's as much about familiarity than anything else. I've programmed full time in Clojure for the last 6 years, and I find it just as easy to read than other languages I'm familiar with (JavaScript, Java) and way less easy to read than other languages like Haskell or OCaml that have their own syntax lineage. I'm sure if I spent an amount of time becoming familiar with them, I'd find them just as easy as I do Lisp.

There are certain things that languages can do that make syntax easier; for instance, Clojure's default constructs reduce a lot of parens compared to CL by using brackets [] and removing nesting. For instance

    ;; clojure
    (defn sum-and-square [a b]
      (let [sum (+ a b)]
        (* sum sum)))

    ;; common lisp
    (defun sum-and-squaer (a b)
      (let ((sum (+ a b))
        (* sum sum)))
This leads some people to assert (with other reasons too) that Clojure is not in fact a "lisp."


    ;; common lisp
    (defun sum-and-square (a b &aux (sum (+ a b))
      (* sum sum))


    ;; clojure
    (defn sum-and-square [a b] (let [sum (+ a b)] (* sum sum)))
Honestly, I'm not sure what you're trying to show.


I'm not sure what you are trying to show? Code can be in one line? Okaaaaay...

I was trying to show that one can define local variables without adding another list level via let.

Like if one would/could write in Clojure:

    (defn sum-and-square [a b &blah sum (+ a b)] (* sum sum))
Another Common Lisp example, we'll stick to your one liner format, using infix syntax via a reader macro:

    (defun sum-and-square (a b) #I(sum=a+b,sum*sum))
Before you ask: what am I trying to show? This shows that Common Lisp syntax can be deeply extended by the user and that this is a standard feature of the language. Want a shorter infix notation without s-expressions? Why not...


Reader macros are global and stateful, this is one of the worst things about Common Lisp. Excellent feature, pity we're forever burdened with the implementation.


> It's like RPN

That's genuinely fascinating for me, because I find postfix notation to be more intuitive and easy to parse than infix notation.


It's definitely easier to parse programatically, but IMO it's a nightmare to read as a human. e.g. compare these expressions:

  (x+y)^2 = x^2 + 2xy + y^2
  x y + 2 ^ = x 2 ^ 2 x y * * y 2 ^ + +
I find it nearly impossible to parse this without actually maintaining the stack in my mind.


> I find it nearly impossible to parse this without actually maintaining the stack in my mind.

You don't write out RPN in a long sequence like that. I'm a devoted RPN advocate and seeing "x y + 2 ^ = x 2 ^ 2 x y * * y 2 ^ + +" is indeed difficult to parse. But that's not at all how you use RPN.

You compute the problem going from smaller units to larger units getting intermediate results as you go and combining them. You'd never write out the sequence as you did and try to follow it after the fact.


I went through EE using an HP 49G+ and continue to this day using RPN in Emacs calc-mode.

>> I find it nearly impossible to parse this without actually maintaining the stack in my mind.

> You compute the problem going from smaller units to larger units getting intermediate results as you go and combining them

That's totally it. I've tried doing infix/standard algebraic notation on a calculator and unless the screen is big enough to see all of your brackets you're almost certainly going to screw up one of the big terms in a fraction or something like that. I think a good different way of stating the "intermediate results" part is that instead of computing the solution from left to right you're computing the solution from the inside-out.

By far the most satisfying part of doing computation in RPN is that moment at the end when you've got a big stack full of intermediate results and you just do that "+ + + + +" to add them all up and bam! Answer!

Edit: I had two thoughts back-to-back after writing that.

First thought: I do actually struggle sometimes with doing math in Lisp because prefix notation isn't a way that I'm used to doing math. Which led to "Hmmm... what if you did Lisp but with postfix notation instead of prefix notation"

Second though: "Ohhh... that's Forth. And you don't need brackets at all..."


> Ohhh... that's Forth. And you don't need brackets at all...

You still need them if you want variadic functions. Symmetrically, you don't need parentheses in lisp if you don't want variadic functions, as long as you're willing to draw an explicit distinction between calling a function and referring to it.

You might as well just make that distinction by using the parentheses, though.


I'm convinced a large part of the benefit people see from RPN calculators is that you have to figure out the order of operations before you start. It forces a way of thinking about the equation which is less error-prone, you're more likely to notice mistakes as well.

Takes a little getting used to, but the interesting thing is that analyzing an equation for the proper order of operations on an RPN calculator is not at all an error-prone process. It's a careful process, and it forces an understanding of the equation itself which just churning through it on an infix calculator does not.


How would you write this algebraic identity?


You write equations in the usual algebraic form. You don't write RPN on paper (or screen), that's not a thing.

When you're going to compute a value on the calculator, that's where you use RPN starting from the innermost computations working outwards, because you have a stack to keep all the intermediate results handy.

Again for like 2+2 it doesn't make any difference, but if you have a long formula with lots of values and different operations, it is much easier to do correctly and fast with RPN.


But this was a super easy sequence.

Can you give an example how it should be written?


It's exactly how you would do the calculation if you ran it by hand, unlike the regular algebraic notation. I am flabbergasted that anyone uses the latter, especially as for most of the history of mathematics calculation was done by hand!


I don't "run" x 2 ^ by hand and I don't think anyone does. Some people find different notations more intuitive and that's fine but whether it's prefix postfix or infix, it's still an abstraction.


> I don't "run" x 2 ^ by hand and I don't think anyone does

Of course everyone evaluates expressions symbolically: in code review, when debugging, even when looking over an expression just after typing it to make sure it’s right.


Many languages have different verb order - English is notionally SVO (subject-verb-object), while Hindi is SOV.

That is probably much more related to why we use prefix/infix/postfix notation: because it maps to our native languages better.


For simple stuff, sure. Try -b±√(b²-4ac))/2a. Or anything complicated really. You end up counting parenthesis, writing down intermediate values, and just blowing it and starting over.

It's really nice to have something complicated, being able to sanity check intermediate values, and having a visible stack made things crazy easier.

I had friends in high school, for anything non trivial they would call out "anyone get 123.5 for the answer?". Look at me, and if they didn't get my answer they would try again.

So sure, postfix is easier for the simple stuff (which is easy either way), but postfix is MUCH easier for anything non-trivial.


> For simple stuff, sure. Try -b±√(b²-4ac))/2a. Or anything complicated really.

In anything complicated and long is where RPN shines the brightest!

If it's 2+2 I don't care whether I type 2+2 or 2 2 +. But for long complex equations, give me RPN every time. All my calculators are set to RPN mode (if it doesn't have RPN mode, I'm not using it).

I know it takes a bit of getting used to, but once you do it is a huge advantage. Particularly great for university tests where speed matters.


Indeed, great for speed and correctness.

It also makes awesome use of a multiline display, even an extra 3 lines to see the stack is a game changer. Without RPN I just use the extra area for graphing.

It's especially useful for unit conversions, since with prefix you never see the intermediate values.


With Rust do you close all your } manually? Do you have an example of Rust code where the equivalent Lisp code would be harder to parse, assuming equal familiarity with both languages?


> With Rust do you close all your } manually?

You don't have to, because you don't end up with }}}}}}}}} in Rust, because statements end with ; and not }.


I didn't end up with ))))))))) in Scheme because it was easy to create smaller functions. I do see rust ending with

      }
    }
  }
In TypeScript I'll often find myself ending blocks of code with

        });
      }
    });
  }
I'm having fun with TS, but I do miss the parentheses. They were simpler.


1. Lots of small functions lead to code in which bugs often hide in plain sight, see this old memo by John Carmack: http://number-none.com/blow/john_carmack_on_inlined_code.htm...

2. Scheme is better than Common Lisp when it comes to Lots of Irritating Superfluous Parenthesis, since declaring a variable in Scheme (using 'define') doesn't create a new nesting like it does in Common Lisp (using 'let').


I do, but there are about 4x fewer }'s in Rust than there are )'s in Lisp.

Look at these:

https://rosettacode.org/wiki/Archimedean_spiral#Common_Lisp

https://rosettacode.org/wiki/Archimedean_spiral#Rust


I'm tackling a significant lisp project right now, and the thing that holds me up right now is that the code is difficult to organize. Python, java, rust, go etc have well-defined patterns to figure out where code lives and where you might expect certain behaviors to occur. With lisp you can really shoot yourself in the foot very easily by using abstractions that are difficult to follow and are spread out across many files.


I like to compose programs from small one purpose Common Lisp libraries, each is a Quicklisp project. I used to write larger monolith CL projects, but I like my newer approach better. Off topic, but I take the same approach for my personal Python and Racket projects also.


> the code is difficult to organize

we can simply do `(defpackage :mypackage (:use :cl))` then `(in-package :mypackage)`. Since they don't have to follow file hierarchy, we are free.

That's good for the long term, and general flexibility. But it's different, sure. Good luck!


I mean, separating files (or modules or libraries or whatever) seems somewhat orthogonal to my point—where you expect to find behavior, and what behavior you expect, rather depends on how you use lisp.


If there's one language in existence where you can shoot yourself in the foot using abstractions it's Java. It produces spider webs of dependencies, hierarchies, etc.


Right, but that's predictable and straightforward to manage. Lisp is far less structured, meaning there are far fewer guidelines or clear indications of intent built into the organization of the program. Or at least, there's enough complexity it's much more difficult to internalize than Java, even with all its verbose warts.


Lately, I've been creating protocols that are meant to be the interface for some kind of entity, such as "user", "utility", "configuration", "auth", and so on. The logistics of that are to use defgeneric for the protocol interface, defmethod in the same package, but can be over-ridden with a more specific defmethod in another package if necessary. I still export regular functions too, but I look to the defgenerics as the main entry points for a particular package. To (sort of) enforce some separation, I try to keep these packages self-contained in their own system (defsystem).

I mean, it's one approach, but my advice would be to take some time to come up with what would work for your appication and CL is flexible enough it can probably support it.


I'm trying to understand why lists matter. Yes, there are some oddities where some base constructs don't play as well with hashmaps as they do with lists, but it is far from difficult to work with a hashmap. Or an array. What difficulty are you talking about, specifically? (Genuine question.)

Biggest difference I know of, off the top of my head, is the LOOP macro having constructs that care which one you have. Curious if there is more.


O(1) access to the nth item turns out to be more important than O(1) insertion.


You'd be surprised how many exponential loops are in successfully run stacks. :)

But yes, when it matters, it matters. Luckily, it isn't hard to use appropriate data structures. Also luckily, a surprising amount of code will linearly process data.


More specifically, the linked lists are too inefficient for modern computing to be the default data structure. Not enough locality, too much jumping around memory. Reality is a tree, but to organize it, you have to convert it into arrays, maps, and matrix multiplication.


Reality is a graph. We understand it as a multi-tree. But we write it in lists.


The reality of a computer is an array since memory is sequential addresses and programs deal with data in memory.


It's funny that behind the scenes in all languages, everything is lists (in the AST).

It's strange that we tend to initially code in easily readable built-in control structures (if, switch, for, while), but then there always comes a point when you need to refactor...and convert them into lists of data structures and process those.

For example, if you are matching routes in a web server, you can write a bunch of if-statements. Very simple. Easily understandable. And you can use whatever criteria you want, in whatever order you want.

    if (request.pathname == '/foo') { return foo }
    if (request.pathname == '/bar') { return bar }
But say now you want to print a list of all the routes and how they are matched.

Most people create a concept of a `Route` object that contains a predicate, and then you loop over those in a list. It feels super clean. But now if you want an exotic way of matching a particular route, or you want route priority or anything like that, and your route matcher and Route objects start becoming really complex...but if you did it with a simple code block with if statements, it would have been really easy.

    const routes = [ 
      {pathname: '/foo', action: () => {}}, 
      {pathname: '/bar', action: () => {}}, 
    ]
    for (const route in routes) {
      if (route.pathname === request.pathname) { return route.action }
    }
If we could have referenced our if-statement code blocks as a list (using Reflection or something), then we could have avoided any abstraction and stayed totally flexible.

I could easily throw a few more requirements at you, and you would quickly have some frankenstein Route object and matching logic.

It's this weird process of "dont-repeat-yourself" where everything looks the same and you abstract it, and then you realize its not all the same, and instead of back-tracking the data structure, you just tack on new stuff and more complicated logic.

Complexity in software stems from these pre-mature abstractions.


It's not true that everything is a list in most languages. Typically an AST is built with both lists and structs. Special forms are always going to exist and the code needs to deal with them, so this isn't a loss; it's being more explicit. Well-named fields are better documentation and faster than accessing the nth item than a linked list. (You can emulate named fields using just lists, but it's just a convention.)

Also, in most languages, the lists aren't linked lists. They're typically array-backed.

JSON has both lists and objects (which serve as structs) and it's quite popular and usable for representing AST's.


Yes but in whatever language you're imagining here, a "list" probably isn't the kind of list that lisp uses (linked lists of cons cells).


It's vector and hashmap that you need most, not linked list.


I had the same thought about conditions a few days ago and discovered this:

https://discourse.julialang.org/t/ann-package-conditions-jl/...


Lol. The final domino falls!



There isn't a language where you can't find a ragequit file. The Common Lisp ones are absolutely savage.


You are right :) This file is primarily for the ones who rage quit CL, hoping for greener grass elsewhere (and eventually come back).


+1 for writing “processing lists” with a lisp




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

Search: