Why is Common Lisp not the most popular programming language?
daninus14.github.io
daninus14.github.io
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.
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.
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.
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."
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.
Now if your leaders are untalented, you have an organizational issue: you're promoting schlubs. Why are you doing that? Stop doing that.
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.
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.
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.
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.
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.
> 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.
I think that's why Python beat Ruby. :p
But hate? Really?
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.
When I said it is kind of the anti-Lisp, I wasn't thinking of it as credit...
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.
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.
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.
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.
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).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.
"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.
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.
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.
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.
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. :)
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?
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) 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? (loop for i below 10 when (zerop (mod i 3)) collect (* i i)) (iter (for i below 10) (if (zerop (mod i 3)) (collect (* i i)))) 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.
let xs = [x*x | x <- [0..], x `mod` 3 == 0]
You can decide later how many you actually want.
(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…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?
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.
hmm... maybe I need to look at the packages that use common lisp.
> copy list
Almost all functions have a copying and destructive variant.
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.
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.
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.
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.
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 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.
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.
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.
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 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...That's genuinely fascinating for me, because I find postfix notation to be more intuitive and easy to parse than infix notation.
(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.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 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..."
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.
Can you give an example how it should be written?
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.
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.
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.
You don't have to, because you don't end up with }}}}}}}}} in Rust, because statements end with ; and not }.
}
}
}
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.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').
Look at these:
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, 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.
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.
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.
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.
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.
https://discourse.julialang.org/t/ann-package-conditions-jl/...
I didn’t know that conferences could potentially generate a lot of revenue to help fund programming language development. I’m quite intrigued by this idea, actually. Thanks to advocacy of the language, there are probably thousands of Common Lisp developers in the world, many of whom are working on interesting projects. Perhaps a series of profitable Common Lisp conferences will provide the spark needed to come up with a modern Common Lisp foundation that is able to support further development of the language.
Your order is messed up: work on Dylan commenced years after that AI Winter had begun.
If that had happened, the web would have been fast 15 years earlier. Things like Flash wouldn't have ever happened. ES2015 would have never been necessary. HTML would have gone away. CSS would have never existed. Declarative web frameworks would have become a reality decades ago. WASM wouldn't have happened. Even stuff like the App Store likely wouldn't have happened because Scheme would have been fast enough that Jobs would have stuck with his initial web app idea.
Chez Scheme was fast but would not fit in the browser especially on Windows 3.1, which still had the largest share when I started JS in 1995, if memory serves -- it was certainly important to Netscape because Microsoft started bundling IE into later Windows versions, as default browser in Windows 98. Petit Chez Scheme was not fast, and I'm not sure I would have had time to use it in those ten days (I never looked at the code).
Guile was out because of the GPL, not my choice (same as with Make It Look Like Java order) and probably no time to embed, or just no way with 64K segments.
If you know of another fast-in-1995 Scheme that might have fit, links welcome.
Of course, everything said is hypothetical as we can't actually wind back the clock.
You could have rolled your own Scheme instead of adopting a then-fast implementation. The Scheme would certainly have been faster from the very start if for no other reasons than integers are faster than floats and real arrays are faster than hashtables.
Scheme would likely have been a bit faster from the start, but the real question would be the trajectory from there. JS needs inline caches and hidden classes to be fast. I don't see how all that could have possibly run on typical systems of the 90s. In contrast, there's a lot of Scheme optimizations that would work perfectly well on those machines before resorting to the really heavyweight stuff.
Considering that JS with many millions in investment is generally slower in typical code than something like Gambit or Chez (both of which have very little financial backing) leaves me assuming that the Scheme trajectory would always be better.
This just leaves the hypotheticals of Scheme's other effects.
Anyone who's used basically any lisp for web work would understand why HTML would become effectively subsumed. S-expressions make dynamic manipulation of the raw trees very easy as that's what a lisp is designed to do. React revolutionized frontend development in 2013, but a huge part of what people like most about it would have been obvious to web developers years earlier.
XML may well have never happened either. While JSON was apparently only obvious in hindsight, the idea of sending S-exp over the wire then parsing out the data is very in-your-face with lisps.
CSS is a much more simple matter. One of the major syntax proposals used S-expressions and their easy integration into Scheme would have all but guaranteed their adoption.
WASM likely wouldn't be needed because as has been shown with Common Lisp, you can already get very low level with just lisp (and even lower if you allow stuff to optionally handle its own memory). Asm.js got really complicated to the point that WASM made more sense, but with performance already being very good, it becomes a lot less necessary (even without those lower-level changes).
Flash was a bit hyperbolic. It would probably still have existed for a variety of reasons, but probably wouldn't have become a separate language and ironically would probably still exist today as a development layer over the browser's features.
Steve Jobs was very open that he didn't want an app store, but instead wanted web applications. There was an issue with the browser's capabilities, but the biggest issue was that JS engines were still just too slow in the late 2000s (part of what killed off the very amazing webOS). Scheme would likely have been quite fast by that point making the web app argument much more appealing and possibly saving the world from fractured ecosystems.
Of course, there's the counter-argument that the world would never accept a scheme and it would have either been replaced or moved everyone into the nightmare of Java applets. There are certainly a lot of alternative universes where the world would be far worse if we'd wound up with Scheme rather than Javascript.
1. JS via asm.js paved the way for WebAssembly, and asm.js (https://asmjs.org/) like Wasm is statically typed. Scheme is dynamic. So there would have to be an asm.scm or similar evolutionary path, in order to get to something like Wasm even if in addition to Scheme rather than JS, for the needed win.
Polymorphic inline caching etc. is not enough to compete with native, e.g., to run Unreal Engine 5:
https://x.com/spatialweeb/status/1757581115609817236
Static typing matters beyond raw CPU perf, in order to prove memory safety with definite allocation -- no touching the GC or 30fps gameplay falls over.
2. Wasm is binary, and this matters too: while transport compression helps JS on the wire, or under the alternative, would help Scheme on the wire, the memory cost (plus CPU of LZ) blowing out the compressed payload into source form for parsing is too much for mobile, or for any head to head competition with the likes of PNaCl (which ~2012 era Google was flogging in vain -- no way it would get into Safari or other browsers).
Yes, binary syntaxes for Lisps exist, but it's not the case that everything can be done "with just lisp".
Please use factual yardsticks and apples-to-apples even for counterfactuals that would have to survive in the same ecosystem.
One more true believer:
>> trueJSBelievers = 100000000
100000000
>> trueJSBelievers += true
100000001Bignums are not faster in general and more work, but yeah: optimizing float (or bignum) to machine int is a win. This was one of the first type specializations done in the JS JIT wars of 2008.
- UNIX is C, so C-family languages won.
- The network effect is by far the most dominant force in the matter.
All of us would like to believe that we operate in a world where technical merit determines memetic fitness, because our jobs seem to depend on it: after all, if we build good things, we immediately see the consequences, and others must also see and appreciate these consequences, right? By doing well at that, we succeed, right?
Well, it turns out it's not so much like that actually.
Turbo Pascal was responsible for the Pascal success. And then came delphi along and somehow lost against Visual Basic.
Some chunks of Windows were also written in Pascal, using Microsoft Pascal which they'd been using since their CP/M days.
MacApp was the framework Apple wrote and promoted, but it wasn’t the primary API; lots of developers wrote directly for the Mac Toolbox or used any of a number of other frameworks like THINK Class Library.
The rest of the world looked very differently at that time, and compared to mac had much better development environment.
I like a lot of the ideas behind Lisp-like languages but I dislike writing in them. Parenthesis, as others mention, start to get unwieldy. I often feel like I'm expressing myself "in reverse", almost like I'm programming in Yoda speak. I mean by that, it often feels like I want to edit/add-to the beginning of a statement rather than the ending of it.
Perhaps the discomfort would dissipate with use. But to be honest, I'd rather just grab Python or even Typescript these days for the kind of ease and flexibility where raw performance doesn't matter. And there are other programming languages that excite me more than Lisp (e.g. Elixir, Unison) so my limited free time has plenty of outlets on that front.
What surprises me is that Lisp evangelists have this default assumption that their language ought to be entitled to popularity. They have some kind of "if people only knew" mentality, just like in this article, where they assume that the only reason people don't flock to their language is ignorance. Perhaps it doesn't occur to them that people try it out and then leave and never come back.
Once the code is data is code clicks there’s no going back. When you read other languages you just see a lisp DSL, you can’t unsee it because you’ve changed.
LISP, (similarly to Haskell) requires you to bend your mind and pay an upfront mental cost in order to access it's benefits, which are that everything is equally easy to describe. No construct in LISP feels like it requires you to bend the language in an awful way because LISP is a fantastically generic tool, some things that are common and easy in other languages are more difficult in LISP, but as you get into the weeds building more complex constructs it will never get in your way.
(and since i mentioned it, the benefit of Haskell is that you can make a LOT of statements about your code you know to be true)
Perhaps different people are inherently more or less compatible with different languages?
Functional languages are like being forced to fry an egg while on LSD. Eventually, you get the hang of it, and you start seeing Spinoza's God in the form constants, but holy shit was it difficult! It's a totally disorienting and mind-bending challenge of re-learning how to do the most basic things!
I think this is true. Peoples' minds work differently; different languages fit different minds better.
More recently, I've felt that way about Python, and about a few chunks of my favorite blobs of self-written C++.
i personally cried a few tears when learning java, whereas the weird drscheme class made me all calm and happy
programming also blends a few layers into one, and some people are operating at one (line by line modification of data), some want generic infinite freedom[0], you can rapidly see who will enjoy what there
[0] one trauma i got during studies is failing an exam because after reviewing ada's manual twice, I couldn't find the page explaining the `object'field` syntax and failed not being able to split a string. i never liked ad-hoc syntax much...
One big problem w/ Lisp is that it is so difficult to distribute programs written using it --- I'd give a lot for the ability to compile to a stand-alone program which could then be distributed.
https://lispcookbook.github.io/cl-cookbook/scripting.html#bu...
Of course, it helps that Rust came from Mozilla and that it’s used in Firefox. Other than Grammarly and some internal Google products, I don’t know any modern-day high-profile projects written in Common Lisp. What would help bolster Common Lisp would be a high-profile project that would be much more difficult to implement in other languages.
Rust does that, this is lifetime elision. If the compiler complains about missing lifetime annotations, that means it can't infer the lifetime. Granted that it not-infrequently feels like it should be able to, and there's definitely room for improvement on its lifetime inference. It's conservative, meaning that an elided lifetime will always be valid, but some lifetimes which could be elided get missed by inference and need to be added anyway.
But most of the times that I've yelled at the compiler for complaining about missing lifetimes, it was right and I was wrong.
One of my use cases was something like a mutable borrow of an struct in a struct in a single threaded code path wasn't allowed because the entire struct was mutably borrowed or some nonsense.
I believe the "idiomatic" way was to create some weird abstraction to appease the borrow checker that the thing being borrowed once is only being borrowed once.
I rewrote the code in C++ and never looked back.
Say your a C developer, or C++ developer and I am a Rust developer trying to sell you on Rust. You have to learn a new language. No way around it. Even though people may have problems with this statement, at a high level it is close enough, I can sell you on Rust probably by saying, 'it C/C++ with better (subjective) or more modern features and extra constraints (think borrow checker) to guarantee memory safety.' I would not be able to make that same argument with LISP.
The next question could be why this could be true, or more importantly, why are so many people using C-like languages. I'd probably place it on portability and UNIX from when C emerged. And LISP machines died.
In other words, I don't think it would be appropriate to try to draw a comparison between the two with the two of them in a vacuum. If LISP machines survived and were popular today, we could see Rust today having the same popularity of LISP today.
That's when you want the uniformity and clarity of static type declarations and C++'s <algorithm> header functions. With Rust, you're just adding modernity and safety on top of that. Great! Lisp is crazy lone wizard shit.
BTW you get many static type checks at compilation with SBCL, instantly, and see Coalton for more (Haskell-like on top of CL).
C considers macros as bad because they are dangerous and have all kinds of weird side effects that break things. If they had all the capabilities of lisp macros, you wouldn't be hearing all the complaints.
Which some of those same problems could have occurred in C at some point, but C eventually converged. I can be confident my fairly simple C programs will compile just fine whether I use GCC/Clang/TCC, etc. If LISP could have converged like C did, it could be a completely different story, I wonder.
On the other hand, you can quickly throw together some crappy Python code to do the same thing, but you end up paying 10x the cost over the long run in debugging and rewriting.
So my advice for people who care about the code they write is to pay the upfront cost to do it well. The good news is that the learning curve does get less steep eventually, and at that point you feel like a goddamn ninja.
I’m afraid these are also problems in Common Lisp.
And... this is kind of the point of the post. The python community doesn't just push to advance only the language, they advance the use of the language in the free software world. What is the closest that the common lisp world has?
CL is expression based (like Rust, unlike other mainstream languages I've seen), has a concise macro system (more convenient than Rust's imo), has a GC (simpler to use than languages with manual memory management or RC-only), has a better developed ecosystem then some new languages (ex. automatic ffi generation; while buggy, tremendously helpful compared to writing bindings manually). And it's not pure as in Haskell. And it has type annotations that may be checked at runtime or improve performance. An implementation like SICL could make it viable to use it as a scripting language.
Any similar modern languages with better tooling/ecosystem? Perhaps Julia, haven't seen it yet.
- Quicklisp, which is pretty handy, since we can install and use a library from the Lisp REPL, without restarting anything.
- Qlot, that installs dependencies locally, and allows to pin them (yeah you can't do that with QL, although cloning a lib and using it is easy).
- ocicl, a new package manager from the world of containers
- CLPM, another new one
- Roswell, if you really really want to install libraries from the terminal (although Qlot does it) or to install implementations, or software.
I bet the ecosystem is bigger than many think: https://github.com/CodyReichert/awesome-cl and anyone would be amazed by its stability.
And that’s a good thing.
I also can’t see how that would make it more popular.
I don't know if that makes me a lisp person. I just know it makes programming fun for me.
(While I've never used CL, I do use Clojure and similarly find it huge amounts of fun.)
I find that no matter what language I use, I eventually want code that writes code. Other languages have bad tools for this. Sometimes I use an editor, or something else like m4, to generate code.
Haskell has Template Haskell, which is icky in a variety of ways, and it has other stuff that I have a hard time figuring out because I’m not a math PhD.
In Lisp I just write a macro.
Also, I agree strongly with the post below. I like Common Lisp precisely because it is old and unchanging. I like that I can get decades-old used books that are still current. I like that the next compiler version won’t break my code.
But it has a full-fledged macro system, the kind where you can write an anaphoric if. It doesn't have the Zen of macros in a Lisp, but it has the power, and that's the important part. People have used it to add ML-style pattern matching and Pythonic f-strings, symbolic algebra systems which manipulate the source code, they're genuinely full-featured.
It also features multiple dispatch, every function is a multi-method and the type system is designed to support this. The community of practice is quite REPL-focused, although remote REPLs aren't as far along as they are in Lisp world. There are some cases where Revise can't invalidate old code, but most of the time it has the "hit save, use the new program" special sauce.
The core language is heavily inspired by Common Lisp, by way of Dylan, and it shows. There might be some other feature of CL which you miss, but it won't be macros.
I doubt Julia gives as many image-based tools for the REPL (no conditions and restarts?), no single-file binary? I guess Julia's ecosystem is less rich than CL's outside of science.
CL doesn't have opinions, it's a toolbox.
I don't know if that makes me a blah person. I just know it makes programming fun for me.
> Be kind. Don't be snarky. Converse curiously; don't cross-examine. Edit out swipes.
> Please don't post shallow dismissals, especially of other people's work. A good critical comment teaches us something.
and many others that at least suggest you could have expressed your sentiment in a more thoughtful way: https://news.ycombinator.com/newsguidelines.html
Programmers who get into CL will hit various silly obstacles:
- No standard way to express special characters in string literals.
- No standard Unicode support; no \u1234 notation in the standard. I/O with character encodings is implementation-specific.
- Weird pathname handling that is simultaneously too abstract, and too nonportable, which is oxymoronic. The pathname abstraction caters to features of operating systems that basically no longer exist. Yet at the same time, two CL implementations on the same modern OS (POSIX or Windows) cannot agree on all the details regarding how a pathname string (the real artifact seen by the OS) parses to a pathname object!
- The experience of just firing up a free CL implementation (no Emacs) out of the box and experimenting in its REPL is poor. CLISP has built in history recall and editing; I would recommend that.
- Ironically poor REPL experience in the free implementations, given that Lisp gave us that word. Only CLISP has built in editing and history recall out of the box. Telling newbies they have to learn a specific editor and its Lisp integration is poor and adds to the activation energy.
- Working with objects can be verbose with syntax like (slot-value point 'x) which is just point.x in many other languages. This can be shortened by defining accessors, but accessors abstract a lot. Given (x point) you no longer know that it's just a simple slot. It's a function call, which could be anything. Another thing is, would you give an accessor a one letter name like x, even if it's in a package?
- Newcomers to Lisp do have to learn the list processing. People coming from languages in which lists are collection bags get confused why their list x is not changing after (append x '(1 2)). If you don't use the loop macro and want to collect items into a list, you have to learn the ritual of push-ing items into a stack, and then using nreverse at the end. Guy Steele described a nice procedural list construction API in Common Lisp, The Language, second edition, but it's not in the standard.
- Working with multiple values is verbose, with forms like (multiple-value-bind (quotient reminder) (truncate 1 2) ...), and multiple-value-setq, and whatnot. Some pattern matching or binding libraries help with that.
This times 100. There are only a few places where the ANSI standard got it very wrong, but this is one of them. Logical pathnames were intended to abstract away different file naming conventions but what they actually did was cast in stone some anachronisms that are incompatible with modern Unix file naming (e.g. case-sensitive path names. Whoops! Can't handle that.)
The good news is that you can just ignore logical pathnames and use normal native pathnames, or use the illogical-pathnames package which largely fixes the problem.
- unicode: major implementations support it.
- oh yeah. UIOP (shipped-in) and libraries fix pathname handling. https://github.com/Shinmera/pathname-utils - https://github.com/fosskers/filepaths - etc
- agreed, it's a pity for SBCL. See https://github.com/lisp-maintainers/cl-repl (although an editor with Slime-like integration is better)
- yeah, it's a choice to make during development. It could be point-x. Or using with-slots.
- yeah
- but multiple values are very helpful (reminder to the reader: they are not like returning a tuple. Return a list if you want). They allow to not break an existing ABI. You can add a return value and not change all the callers. Awesome.
I just always wrap SBCL, etc. in rlwrap when started on the command line, in fact I alias SBCL to rlwrap <full path to SBCL>.
When I start SBCL in Slime, I use the full path in my .emacs file.
Easy problem to fix.
and seriously, what is the mental difference between say (print 'hello') and print('hello'), for me the first is easier. They both have the same number of parenthesis, but in the first I can see all the operations that go together.
Hating python every time I use it. Python is lies all the way down. "You don't need curly braces or anything to mark your code blocks, only indentation"... until you want a multi-line item, in which case you, well, wrap it in parenthesis.
foo = ("a very" "long" "string")
is valid python, and so is foo = ("a very", "long", "string")
but they give different results. vs (cat "a very" "long" "srtring")
> is valid python, and so is foo = ("a very", "long", "string")
> but they give different results.
That's because the former creates implicit string concatenation, while the latter is 3 distinct strings.
FWIW, while I love Python, I think implicit string concatenation is a huge anti-feature. It is a source of bugs and breaks the Zen of Python which states that explicit is better than implicit.
Your first line should by a syntax error, as far as I'm concerned.
Above might be interpreted as a case for a language with semantic whitespace and w/o excessive parentheses, e.g. like Python, but there I find it all to easy to introduce subtle bugs during refactoring.
All that was decades ago. I haven't written a new program in LISP in decades. (In later years Python, Go, Rust, C++, and when forced, JavaScript.)
* Early thinking was that programs needed to be able to modify themselves. This turned out to be a niche feature. It was a holdover from the early days of programming, where storing into the code was considered a normal activity. Von Neumann was into that. To index into an array, load instructions were modified in place. Once index registers were invented, that mostly became unnecessary. Languages can be too dynamic. You pay a price for that. Python suffers from the design feature that anything can store into anything at any time. This blocks most compile-time optimizations.
* Lists as a primitive are OK, but modifying lists all the time isn't what you really want. Most languages have settled on a useful set of collections today, and their implementations, especially hashes, are highly optimized. Python pretty much got this right, and other languages now use roughly Python's various collection types.
* Saving the entire state of the system, instead of having source files, was a really bad idea for anything that had more than one person working on it.
* The overly clever macro system makes code hard to maintain. (I tried to port [2] to Common LISP. Got about half way. It has the original Oppen-Nelson simplifier inside, the first of what's now called a SAT solver. Originally written in MACLISP (MIT's Project MAC, not Apple Macintosh), and ported to Franz LISP by Oppen and Nelson, it has too many clever macros that I wasn't able to completely figure out a decade ago.) This is a generic problem with macro systems, of course, which is why C deliberately had a weak macro system.
LISP is a blast from the path. It's fun for retro reasons, but things have moved on.
(BTW: CL isn't Smalltalk which isn't uniquely that anymore, we do use source files and we can compile single-file binaries. My web app weights 35MB, starts up in 0.4s (or 0.01s without core compression))
https://lisp-journey.gitlab.io/blog/these-years-in-common-li... (https://www.reddit.com/r/lisp/comments/107oejk/these_years_i...)
A curated list of libraries: https://github.com/CodyReichert/awesome-cl
Some companies, the ones we hear about: https://github.com/azzamsa/awesome-lisp-companies/
and oh, some more editors besides Emacs or Vim: https://lispcookbook.github.io/cl-cookbook/editor-support.ht... (Atom/Pulsar support is good, VSCode support less so, Jetbrains one getting good, Lem is a modern Emacsy built in CL, Jupyter notebooks, cl-repl for a terminal REPL, etc)
(It was quoted on HN in a previous discussion, and I was impressed with the credentials of the person quoted, but I don't have the reference handy.)
Every program, every module, is potentially its own DSL, so good luck getting programmers to agree on a convention.
At least with more rigid languages like Java or Rust, when you open a file, you'll agree what language it is.
Haskell has this problem, too. You need things like "Boring Haskell Manifesto" to discourage too much magic.
Haskell has "pragmas" that modify the language extensions that turn Haskell into a space language. Pragmas can be enabled on a per-file basis.
I like how the Rust toolchain addresses this by letting you gather things like "stable/nightly", "feature flags", "linter suppressions" in a config file at the project root, so that a team can agree on a set of conventions and have the toolchain sync with those decisions made in one place. This isn't strictly speaking programming language, but tooling, but it plays a big role in team governance and cooperation.
it isn't, don't worry. Problem solved ;)
- Scratch
- python/perl/ruby/javascript
- C/Java
- Swift/Rust
- Anything else running on the jvm
-
- a whole lot of daylight
-
- common lisp.
And many if not most will fail to get to the first exercise, those that do will start it well after all the rest have completed 10.Land of lisp seemed to be the best learn lisp book and it couldn't even manage to limit itself to one implementation of common lisp that you could use for the entire book.
The situation is ridiculous.
Lisps are hard to switch to but I disagree that they're hard to start with.
But you know where you place it on the list. I did sicp w/ racket, a 10 y.o. I know tried simply scheme w/ racket. I'd put it next to anything on the jvm (including clojure) myself. The 10 y.o. puts it way behind python & swift.
But I'd still call it very high on the list relative to common lisp, which is the point.
If you want a really nice language derived from Lisp, try REBOL or Red (derived from REBOL).
See how long it takes you to set up a Common Lisp implementation from scratch. Eg steel bank. Better yet, watch a kid try it on for size. Land of Lisp isn’t all bad but they can’t limit themselves to just one!
Brackets are beside the point.
If a 3 year old has trouble using a hammer, you wouldn't conclude that hammers are difficult to use.
On the other hand if you don't care that's perfectly reasonable and there's not much to talk about.
sbcl —script hello.lisp
Compare that experience to choosing python. Forget the actual language just all the overhead. It's not a close run thing no matter how angry common lispers get about it.
This is my exact experience of common lisp. People aggressively pretend there's no problem other than other people are stupid and have broken minds because they didn't get functional early enough and so on.
Land of Lisp at least tried, right? And did a vastly better job than anything else I've seen. Couldn't manage to do it all on sbcl though, huh? That's a shame, isn't it? Why do you think the best effort failed in something like that?
This industry will mostly use whatever Google/Microsoft/Meta sponsors.
I large culture consisting exclusively of genius composers would no doubt develop styles of music that would relegate our pinnacles of classical music to their “pop”.
No doubt there is a bell curve of how easily each of us intuit novel abstraction, vs. benefit from more predictability (i.e. restrictions, enforced patterns).
Mainstream languages are going to be more predictable and restrictive or patterned, than programming languages auteurs might prefer.
This is, of course, a simplified view of things. For one, I think Intel still pushes a compiler some. That said, I think it is valid enough, in that Intel isn't pushing the compiler for the sake of the language it compiles, but to push some optimizations that they excel at.
There is a question of how explanatory this is. I think it is fairly accurate to say that the marketing and general outreach for many of the successful languages today helped build on their popularity to the point that we know them today. It does not explain the seed of any success, though.
For example, the Common Lisp ecosystem never converges to a single implementation of any single "thing".
Take Rust. You may not like Serde for serialization, but the ecosystem has converged to it. Similarly, Tokio. You use them until you understand why you need something else and then bite the bullet of going against the tide when you can't make them work anymore.
Okay, so what serialization has Common Lisp converged to? Uh, yeah, about that ...
"Lather, rinse, repeat" this for any value of <X> (serialization, async, objects, concurrency, etc.) and anyone sane quickly reaches the conclusion "Just say no to Common Lisp".
—-> Everyone rolls their own!
Its an interesting problem.
Julia suffers from this too, fwiw. Multimethods in general can be strange but useful beasts, but I don't know of any IDE that handles them well. And sure C does this too. But C isn't exactly the model of useful language that lets a programmer dynamically explore what programs they can compose, is it?
Normally, I think most syntactic arguments against languages are bad. But having written in fancy PLs that don't have any kind of postfix/method syntax and those that do, as well as writing language servers and compilers, this is the one syntactic thing I feel very, very strongly about.
LISP will always suck for beginners because in Python and C++ and Rust and Swift (etc) they can add "." to the end of a value and the IDE will remind them what methods they can call, or explore what's available. The IDE can even weight which things to suggest. Other people talk about package managers and ecosystem and so on, but this one syntactic choice is something overlooked by many people when talking about how languages grow userbases. Don't overlook it.
I'm pretty sure it's the people.
Lisp is a lone man sports. There's not much community spirit. There's not much github, patches are not accepted.
In general. Of course there are many great people around, of the anti-academic background. Who tried to build communities. But the ecosystem lacks a lot still.
Glass house?
> Glass house?
A sympathetic reading of that might be: “I’m pretty sure it’s the a-collaborative go-it-alone culture of many of the practitioners”.
Lisp’s famous strength at enabling coding styles tailored to every specific domain, may also be its weakness with regard to low friction code sharing & adoption.
That, and the distance between Lisp’s high dynamic flexibility vs. more restricted imperative or functional coding styles that are easier to optimize at the low level, may encourage more bespoke development practices.
Let's say, ACL2, a theorem prover written in Common Lisp. Unless what Mr. Urban tells you about Lisp users, they have a github repository. They have user/developer meetings. They even presented their stuff at Lisp conferences (I have attended one myself, where I saw a talk about it), wrote books about it, published hundreds of research reports, ... The user group shares > one million lines of Lisp code ( https://github.com/acl2/acl2/tree/master/books ), with many more lines of code developed by users.
Workshops:
https://www.cs.utexas.edu/users/moore/acl2/workshops.html
How does that fit into your theory and what Mr. Urban wants us to believe? Here we have a shared large Lisp code base used at companies like Intel, AMD, ARM, ... where people collaborate over the Internet... how does that fit into your "go-it-alone culture of many of the practitioners" theory?
that must be more than 15 years ago...
Something I have not seen here is the advantages of type (dynamic or static) for channeling consistent library symbol use.
For instance, one of the undersung benefits of class style object oriented code, or any type dispatch, is how it helps enforce the right set of functions to use on some data (i.e. the methods of that object).
This cohesiveness between a bucket of symbols related to a data type makes coding and sharing easier. In whatever form this takes, across languages.
Lisp’s flexibility doesn’t provide this kind of namespace modularizationthat I recall?
Am I out of date or forgetting something?
This kind of helpful context sensitive symbol organization is a big factor for simplifying code sharing at the “syntax” level. Reduces mistakes, narrows choices (in a productive way), etc.
Without it, adopting a lot of outside code is much harder.
Well, if you're volunteering...
All joking aside, I'm not particularly bothered with the relative lack of popularity CL has, its force-multiplying powers make it easy for a relatively small community to create and maintain a decently sized ecosystem.
– Quoted by memory from comp.lang.lisp years ago. (Sorry, can’t remember by who.)
Really, I get the parenthesis hating: It is truly terrible to have to deal with them unless you have good editor support and have learned to use it. But then a funny thing happens: The parentheses essentially vanish! You just don’t read parentheses anymore, you read the indentation instead. In my own emacs setup, I have in fact chosen to de-emphasize the parentheses to the point where they are barely visible. It makes the code so much more readable.
Good editor support means at least semi-automatic indentation of code. Unindented code is of course unreadable, and wrongly indented code is worse. But that is true of any programming language, isn’t it?
"But I used it for X and it was insanely productive!" Great, but there's a lot of programming that is not X programming.
"It's just that the rest of you are not enlightened!" Right... the maharishi's people tell me the same thing.
"It perfectly fits the way your mind works!" It perfectly fits the way your mind works. It doesn't perfectly fit the way most of our minds work.
Lisp winds up not being the most productive language for the vast majority of programmers. So it's not very widely used.
Seriously. Macros have to be understood a priori to be readable. Macros make the cognitive load of the language very heavy.
Contrast that to Ruby which can do rich DSL without macros. It’s much easier to read and figure out even when it gets complicated.
Lisp is a beautiful language. But it hits a wall. Macros were made to hop that wall, but ultimately hurt the language.
I think this can be fixed with pattern matching and perhaps types, but it will take a very smart programmer to pull it off and it will take Lisp in a direction that old die hards are probably uncomfortable with.
Just look at this picture:
https://www.semanticscholar.org/paper/The-programming-langua...
We call that "line noise".
Another part of my work involves the web. There I can use mod_perl, mod_php, and mod_wsgi. What about Lisp?
So every commonlisp codebase has a different flavor, and anachronistic semantics.
No matter how good you are, this is difficult to deal with.
Sure, for config files or writing extensions for your favorite piece of software, you’d make the effort. But most software development is not in that category.
As a result, all the client code looks like it's written in a bespoke language. This, while it's a lot of fun, creates write only code.
You had to show very little before they understood. Now try Lisp ...
M: This is a list
S: What can I do with it?
M: By itself nothing but trust me it is important. This is how we take the head of a list
S: Cool, why would I want to do that?
M: We'll get to that later. This is how you take the tail of a list
S: ...
M: This is how to append to a list
S: Is this going anywhere?
The number of things you are shown but do not understand keeps piling up before they are supposedly going to magically work together to do something underwhelming (when BASIC did very little it took very little to do it). Add to that the function names were either cryptic, "cddar" anyone - "EQ" and "EQUAL", or insanely long
You have too much to learn before you can make sense of anything. Least that was how Lisp was taught to me in the 80s :)
I self taught Forth and Prolog because with both once you learn something you can do something
Readable code needs to be not subtle, low on magic, and self-explanatory. The last one is hard to quantify and make objective, but is imperative in making an approachable language.
(set x (list 1 2 3 4 5))
(for item in x
(do stuff to item))
Now try to explain BASIC, but do it by first explaining how to create and manipulate a block of memory.You are conflating language differences with the real argument which is the decades-old debate about whether introductory courses should be practical or rigorous.
And as for BASIC why would you need to "explaining how to create and manipulate a block of memory". This is not something that the BASIC programmer needs to know to write a program
This is just the sort of elitist crap that gatekeeps becoming a programmer. You can drive a car with only the most superficial understanding of how a car works and it does not affect your ability to get from A to B effectively
The answer to this decades old debate is simple, after each lecture the student should be able to do more than they did before they entered the room. At the end of the course they should be capable of teaching themselves from the available resources
There is absolutely no need for one size fits all solution because it will be always wrong.
Looks like largely rhetoric question
+Lisp Lost the train of microcomputers. They were "toy machines" that needed low level programming like assembly or forth or later C to do anything remotely similar to what Mainframes could do.
+Common Lisp is a mess, a compromise, like Deutsch (a language created to take something out of every variant dialect), designed by committee language. Better (opinionated, created by a person) alternative Lisps exist, like Clojure.
+Lack of (affordable, easy to use) Visual tools to develop, like Visual Studio. No. Emacs is not easy to use for newbies. Visual structured editing for parenthesis have existed for decades now but those tools were not available for normal PC and even today are very expensive, as the market for them are the companies that bought the expensive Lisp machines in low volume.
+Lack of access to the C libraries. Do you want to use anything modern like "dear imgui", iOS libraries or whatever? Bad luck.
+Competition catch up. Other languages copied(badly) the Lisp ideas, lambdas and real macros, functional programming. IMHO they did it wrong, but they are good enough for most people, while not having the problems of CL, like being able to use the C libraries as python and Lua can.
+Other Lisp are working fine. Clojure for example can use the java libraries or even some versions the C Libraries.
???
I recently started learning Common Lisp for fun (and fun it is!) and the ease of accessing C libraries was one of the things that surprised me in a positive way.
Using https://github.com/rpav/cl-autowrap one can simply write (c-include "file.h") and the API defined in "file.h" is accessible from Lisp. I can't think of a simpler way.
Even without cl-autowrap, FFI using https://cffi.common-lisp.dev/ seems simple enough.
Isn't CFFI enough for that?
Here we restart a buggy program from a single point in the stack: https://www.youtube.com/watch?v=jBBS4FeY7XM so we don't re-run everything from zero.
The correct answer to the question is the obvious one: the parentheses. Visually, semantically, practically, expressibly, and legibly. And the typelessness, and the ecosystem, and in general the idiosyncrasies, almost none of which add value.
All the standard counterarguments have been done to death, none of them are any good, but more importantly, they're not how popularity works.
https://github.com/dimitri/pgloader/blob/master/src/pgsql/pg...
This, on the other hand, is perfectly legible if you know the language. I'd say it's even perfectly legible if you don't, but how would I know?
There are a couple good examples in there of what I said in another comment, about how, quite aside from getting the hang of reading and writing sexprs, one also has to learn a rather more foreign discipline of building up linked lists in the idiomatic way. Which is a greater barrier than learning to read `(and foo bar)` as `foo && bar`. That part really doesn't take long.
I'm having fun with Typescript these days, but parentheses are what I miss most from my Scheme days. They're objectively better.
* Packages are harder for divergent languages
* Momentum
Hard to come up with more words to explain it
clojure/cjs/babashka, fennel, janet, racket, etc make more sense to learn.
apologies to SKB.
C++ is too complicated to even bother, and looks a completely different language every five years; I have yet to meet anyone that knows its syntax fully (C++ compilers included). Writing C/C++ means spending your time managing memory and then debugging memory management errors rather than thinking about the problem domain.
Python is very slow and has horrible package management.
Rust compiles slowly and the borrow checker takes a long time getting used to.
Java is bloated and Oracle-proprietary.
No version of Julia could even compile the example of the Julia book I bought, nor some of its own tutoials.
Perl is write-only.
...and CommonLISP? I know important and successful systems have used it in the past, but haven't heard anyone using it for anything I needed recently (but that could be due to my area, search engines, natural language processing and machine learning, where Python libraries - written in C++ to be fast enough - dominate the research frontier).
LISPs incl. CommonLISP and esp. Scheme are elegant, minimalist (Scheme in particular) and lack powerful IDEs and library support (at least since LISP machines disappeared), and excess parentheses make for a write-only experience, especially if you have to read the code of others (your own code may make good use of functional abstraction).
This was either many years ago, or is a skill issue. Post-1.0 Julia backwards compatibility isn't perfect, but it's held to a high standard. Being unable to run its own tutorials is not a thing that actually happens, and the language isn't responsible for bugs in whatever book you were referring to.
If this was pre-1.0, sure, that's what a version starting with 0 means. But after? Skill issue.
(defclass Shape () ())
(defclass Rectangle (Shape) ())
(defclass Ellipse (Shape) ())
(defclass Triangle (Shape) ())
; Having a defgeneric is not strictly necessary in CLOS. The code would
; work without this definition. However, this is good practice as it gives
; us a natural place to document the generic interface methods are expected
; to implement. It also lets us add a default handler with a meaningful
; error message, if no suitable method was found in some case.
(defgeneric intersect (x y)
(:documentation "Shape intersection")
(:method (x y)
(error "Cannot interesect these shapes")))
(defmethod intersect ((r Rectangle) (e Ellipse))
(format t "Rectangle x Ellipse [names r=~a, e=~a]~&"
(type-of r) (type-of e)))
(defmethod intersect ((r1 Rectangle) (r2 Rectangle))
(format t "Rectangle x Rectangle [names r1=~a, r2=~a]~&"
(type-of r1) (type-of r2)))
(defmethod intersect ((r Rectangle) (s Shape))
(format t "Rectangle x Shape [names r=~a, s=~a]~&"
(type-of r) (type-of s)))
>mapsmake-hash-table
It's so great and new that it's not compatible with last week's code...