Package management kind of sucks, the ecosystem sucks, other languages continually improve but nobody will ever advance the CL standard. There is nothing compelling about the language to people who aren't already Lisp people.
Package documentation which seems to mostly consist of a list of variables and function names, possibly with a single sentence describing what the function does. Possibly.
> There is nothing compelling about the language to people who aren't already Lisp people.
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.
I'll never understand why a language needs to constantly evolve to be popular. There's nothing fun about having to keep up with changes, and continuous development just screams to me that a project has not yet matured. When something is stable enough to just keep working for 10 years without an update, that's not a bad thing. When did that become the mindset?
Back in the day, it required machines that were more powerful than what C programs could run on. By the time machines caught up and Common Lisp was standardized it was too late.
Lisp was sort of a testbed for extravagantly expensive features, many of which (not all) became table stakes for newer languages as they got better at writing compilers for much bigger, faster computers.
Because I can eliminate drudgery in a way I can understand.
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.
On the strength of your first four paragraphs, I have to plug Julia. It doesn't have the benefit you point to in your fifth paragraph, of having a decades-old standard; stability of the core language is good so long as you aren't comparing it with C or CL.
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.
It's a young language still. Pre-compilation has brought "time to first plot" down considerably, and ameliorated many (but not all) of the specific complaints in that gist. One stands out, which is "lack of pattern matching", Julia is a language like CL where a language feature can be a library. MLStyle is arguably the best but there are a few[0].
Similarly, there's a start (heh) on a condition and restart system[1]. Julia macros are actually full-strength and that makes up for a lot. I would say that "less mature" is a fair comparison with CL but "less rich" is more of a judgement call. When people are in a mood to be critical of Common Lisp, the richness of the package/system ecosystem is a frequent target of that critique.
Binaries which aren't enormous is a main focus of development now, as I understand the timeline 1.11 will bring modest improvements (in the next few months) and 1.12 will be relentlessly focused on achieving this, it's a recognized problem.
I find Julia completely suitable for general-purpose programming, right now. Moreover I see it on the right trajectory. My hope is that when the trigger is pulled on 2.0, this will include a standardization process, resulting in an actually-stable language by about 2030. Note that Julia is closer to Rust-stable than Python-stable already, post-1.0 code which runs on the latest version (1.10) is more common than not, but I do think compatibility needs to be broken, once, eventually, to fix some bad decisions. It shouldn't have to happen twice.
I realized I didn't address one of the main things you said, which was about image-based tools. That's better as its own reply I reckon.
Depends on what you mean by "image-based". Julia doesn't support reloading core dumps, but it has pre-compilation, and it has Revise, which will do everything it can to keep the REPL state up-to-date with changes to the source code. My experience is that it always updates state (silently) or fails and tells you, and when it fails can be predicted: if you modify the layout of a struct, or change an enum, it can't track that, so the prompt will turn yellow and you'll have to restart. You get the same yellow prompt (instead of green, and this is configurable to use textual cues btw) if there's a syntax error, but in that case, you fix the code and press enter and it goes green again.
Revise evals the minimum amount of code to update the program state, this is instantaneous for all practical purposes.
The Julia REPL is a massive improvement over any of the REPLs which ship with open-source Common Lisps, and head and shoulders above what comes with most other languages. It is not yet up to the standard of a commercial Lisp, or of the Emacs SLIME-mode approach, but the community lives in the REPL (and notebooks), so I expect to see the polishing process continue.
Essentially all the Lisp features except for s-expression syntax and attendant macros have been adopted by popular mainstream languages. CL has already given everything it had to give.
not true, sorry! And if some languages took features, the combination of them is unique in CL. Image-based REPL interactivity, condition system, interactive debugger, restarts, compile-time computing, compile-time checks, incremental typing, speed, a different object system, stability, single-file binaries, connecting to a remote system…
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 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.
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.
> 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.
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.
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.
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.
> 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.
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.
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.
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."
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.
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?
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').
> 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.
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.
> 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.
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.
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.
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?
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.
> 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.
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.
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.
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'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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
> 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.
> 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.
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
> 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.
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.
> 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.
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.
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).
> 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.
> 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 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 no...
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.
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.
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.
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.
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.
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.
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.
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.
Personally I'm not especially interested in programming in a language with a separate function namespace. It makes much more sense to me to treat functions more like any other value in the language.
This is the kind of answer which drives people away: you just told someone doing one hard thing to do another at the same time, and that also has near certainty that they’ll hit some task which is easy in their default editor but non-trivial in whatever new one you recommended.
This is so often mentioned, it must be true. Or is it? To put numbers on it, I counted the occurrences of brackets, braces and parenthesis in Frank Busse's Arabesque cellular automata (just a small program for which I had the C version and a Common Lisp translation, which I happened to have looked at recently for no good reason). The C version had 86 of those characters, while the Common Lisp version 196. Yeah, more than twice as many, but can that truly be a chief reason to give up on a language?
I do recall difficulties with this in the beginning (the first few years in my case ;-}
And I still find old LISP code (the one where symbols were still capitalized) hard to parse. With modern (say, since the 90s or so), well written style where the depth of the expression is indicated by the indentation, I trust that the editor indented correctly, which allows me to forget about the parentheses. I wouldn't attempt to find a syntax error in a CL program w/o an editor supporting the language.
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.
OTOH, I remember reading somewhere that Tanenbaum was happy when Linux came out so that people would stop asking him to make Minix into a real "usable" operating system. There may be something to be said for letting Lisp remain more conceptual/ideal than practical and allowing other functional languages take on the "warts" associated with being used by everybody.
It’s more of a toolbox to build languages than a language.
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.
It's not like you'd often create a named language, it's more like accidentally defining a DSL along with libraries when working on something in a lisp with a decent macro system.
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.
Because most programming is done by people trying to do a job and it requires broad tooling support and familiarity, LISP is a language that has neither.
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)
the upfront mental cost debate is still going, a few people tried teachings lisps as first languages and people didn't struggle
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...
Yeah that's fair, it also came naturally to me but anecdotally I've found a lot of people struggle with it, especially those who came up through a "get shit done first" type python or java pipeline in a traditional institution or bootcamp rather than having a true CS background.
For my comparative programming class in college, I was the only person who got all the Lisp assignments done on time and correctly (it helped that I was deeply into TeX at the time), so there is definitely a mind-frame to be in, and some sort of hurdle which a fair percentage of folks find difficult.
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.
When you build SBCL from source, enable the compressed heaps option. Then when you create an app with save-lisp-and-die, you get a fairly small app even without having a treeshaker to remove unused code.
My web app with dozens of dependencies is ±35MB, using SBCL core compression. So that's heavier than Rust, lighter than Deno, maybe in par with Go when the application grows. It starts up in 0.4s, without compression it does in 0.01s.
I feel like Rust is easy; it's just responsible, 21st century C code with more data structures in the standard library.
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 find Rust so onerous, I gave it a fair shot, and I see it's merits but I just don't find myself in situations where Rust feels like the right fit very often. Where the cost of it feels worth the benefit. Writing C is and always has been an absolute joy for me and almost everything I'd want to do in Rust I'd rather tackle in C with a big smile on my face.
When I learned emacs lisp back in the mid-80s, I didn't find any requirement to bend my mind or pay any upfront cost. It was a little different from C (and BASIC), certainly, but once I grasped a few basic concepts, it felt extremely elegant and very well suited to the task(s) to which I was putting it.
More recently, I've felt that way about Python, and about a few chunks of my favorite blobs of self-written C++.
This is only true in terms of "free" options. There were (and are?) decent paid options that had quite good tooling. In the free software realm, though, it was lacking.
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?
I agree with you about the upfront costs. As a functional programming fan (F# in particular), I can’t deny that it sometimes feels like puzzle solving. Fun, but not easy at first.
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.
Python != Crappy code. Lisp != good code. You can write crappy code in any language. My experience is that it is much less expensive to maintain Python in a team environment in the long run. People aren't tempted to create their own personal "domain specific languages" in Python. Lisp kind of encourages that.
I'm not saying Python is always crappy. It's just easier to create crappy code in Python, due to dynamic typing, rampant side effects, null values, OO madness, etc. If your team has the discipline not to do that, great.
much, much, much less than Python. CL compilers (looking at SBCL) get you many type checks, at compile time, instantly (you compile the function at point), or you can send computation to compile time yourself, and the OO is different, etc.
Rust requires people to learn how to adapt to Rust’s borrow checker, which can be challenging for programmers coming from other languages, yet this hasn’t stopped Rust’s rapid adoption in systems programming. In fact, Rust’s safety features are Rust’s selling point.
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.
I'm not sure if Rust is a good comparison though. Rust is a lot closer to C, it is a language influenced by C, and current popular programming languages tend to also be influenced by C. So while there is a lot different with Rust, a lot of knowledge from, lets say, a C developer can carry over.
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.
I'd argue Lisps are worse today. They encourage a thing that's generally discouraged in C: macro programming and typedefs. You're coding in Lisp, but you're actually coding in your own dialect of Lisp, eventually, with lots of idiosyncratic code that's difficult to reason about. That doesn't scale well in collaborative environments, where many people are touching a vast codebase (which I don't think was really a thing back in the '70s).
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.
That is the biggest problem I have with LISP which is why I think a lot of it is historical to some degree. I do like LISP to dabble in, but yea, there are tons of different dialects, it makes it harder to approach. Because while there is 'Common' LISP, there are implementations that can vary slightly between compilers, which compiler do you use? And some of the more popular compilers are sort of dead? There is SBCL, which does get some development, but not much I think? I don't think newer version of SBCL run on most of the BSDs? And yea, you also end up creating your own dialect in the process.
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.
The 'BSD' on phones would be iOS and tons of languages don't mostly due to the platform.
I think a better example would be, the amount of embedded/appliances that ship forks of FreeBSD, OpenBSD, or NetBSD. Think of like routers. While Linux is the 1,000 pound gorilla in server space, there is still A LOT of BSD servers also. The person dimising BSD usage is only taking into account desktop share not realizing some OSs run on more than just consumer devices.
* a runtime in-memory native code compiler (-> COMPILE)
* a file native code compiler (-> COMPILE-FILE)
* and a runtime native code loader (-> LOAD)
Apple forbids providing compiled languages on (I'm talking about "ON THE DEVICE") iOS. Good luck getting those onto iOS. Their own Javascript runtime JIT is allowed, others are not. Common Lisp runtime native compilers are not. Thus one can't move a development Lisp, which includes (!) a native compiler, onto an iOS device and bring that into the Apple iOS/iPadOS/... Appstore. The Lisps which run on iOS (ECL, LispWorks), are compiled outside of iOS. They may include some kind of interpreter (source or bytecode), without a native code compiler component.
Thus: Other languages are not allowed (by Apple) to compile code to native code on (!) an iOS device. On can compile code outside of iOS and move the code to iOS (as a developer). But one can't use a language which compiles to native code ON the device.
There are other restrictions. LispWorks for example runs on iOS, but provides a special runtime with less capabilities than on macOS, also with a different garbage collector. Without the runtime native code compiler.
There might be some BSD in iOS, but different & less form an actually BSD.
To bring SBCL to iOS one thus would need to remove runtime native code compilation/loading and provide a specialized runtime with a different garbage collector. Then this would be needed to be integrated into some other application as a library, or one would need to implement a new GUI for it...
Although as long as you're not distributing the code these restrictions don't apply. Depending on what you're doing that's either a technicality or that's how you intend to proceed, there's a reasonable amount of iOS code out there which pays no attention to Apple's rules whatsoever, and which you can't download binaries from the App Store of.
I think a domain-specific dialect is the best way to unlock the potential of a team of handpicked experts, but a lot of companies seem to hire an army of devs they don’t trust enough.
sorry but nah, nobody encourages anyone, let alone newcomers, to write macros. You don't even need to. You can chain function calls and get the job done.
BTW you get many static type checks at compilation with SBCL, instantly, and see Coalton for more (Haskell-like on top of CL).
You can code your own dialect of ANYTHING. OOP does this all the time (anyone for Enterprise Fizzbuzz).
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.
I find that borrow checked in Rust is the easiest part of the language to get used to.
Lifetimes on the other hand seem unnecessary in 99% cases where compiler forces you to use them - why can't it just assume that all the required parts have the 'a and complain if something violates that?
Event bigger issue is all the missing features that most other languages have - overloaded functions (they kind of exist in generics), ranged integers, arrays indexed by enums, full support for type aliases, some level of inheritance, even if it was the go style, would make Rust much much easier to use.
> Lifetimes on the other hand seem unnecessary in 99% cases where compiler forces you to use them - why can't it just assume that all the required parts have the 'a and complain if something violates that?
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.
To the contrary I ran into cases pretty quickly that rust just wasn't useful for in my opinion.
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.
From what I have observed, most advanced Rust authors don't bother with lifetimes at all. Every pointer becomes an integer index into some slice and they mostly skip the lifetime/borrow checking. Lifetime are only kept on the few, easy-to-reason about cases. Kind of funny actually.
Forbidding multiple mutable borrows is how Rust is able to prevent you from writing code with bugs like iterator invalidation. Has nothing to do with threading.
If Racket has what they're looking for and CL doesn't, then that's proof that the assertion is wrong, because Racket is far less popular than CL.
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.
I clicked thinking I was going to see some gnarly Lisp code. It's out there!
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.
Generally speaking, the best things are not the most popular. There are probably a few exceptions such as classical music, but I've found this rule of thumb to hold in many different domains.
People’s ability to process complexity must play into where the peak is.
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.
Paraphrasing the point at the end, there is no company that has the financial incentives to promote it. The companies that do have financial interests in CL are ironically not incentivized to promote the free ecosystem around it. For the same reason that commercial C compilers are not really a thing anymore.
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.
Right, I don't know the core details right off, I just meant that it was easy to think of some commercial C vendors that still exist. I think it is fair that there is a difference in having a corporate sponsor, and having a organization chartered to sponsor. (I also have to ack that I don't know of many other C vendors, nowadays?)
I started learning Lisp recently. The text I was reading said people complain about the parentheses and I see a number of people stating that here. But that is not a problem when you have an editor that understands lisp and keeps track of all that for you. Just focus on the code and the editor will take care of the parentheses for you.
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.
Common Lisp once had plenty of backing from major companies like Symbolics, Xerox, and Texas Instruments, as well as smaller companies such as Harlequin. Even Apple owned Macintosh Common Lisp at one point, and SK8 (which could be considered a follow-up to HyperCard) was implemented in it. Had Apple not had its problems in the 1990s, one potential alternate version of Apple I could imagine would be some sort of Lisp OS with a Mac interface (true story: the Newton could’ve had a Lisp OS if it weren’t for C++ advocates winning out). Unfortunately, the AI winter of the late 1980s and 1990s severely damaged the Lisp market; companies either abandoned Lisp or went under. The torch of Common Lisp has been carried on by open source and commercial implementations, but there are no major companies actively backing Lisp in the way that Java is backed by Sun/Oracle and that Python is core infrastructure at countless companies, large and small.
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.
> ...one potential alternate version of Apple I could imagine would be some sort of Lisp OS with a Mac interface...Unfortunately, the AI winter of the late 1980s and 1990s severely damaged the Lisp market...
Your order is messed up: work on Dylan commenced years after that AI Winter had begun.
Yeah, I should’ve clarified regarding Apple, which wasn’t in the Lisp workstation market. Apple’s departure from Lisp has little to do with the AI winter (though Hacker News commenters lispm and mikelevins may have a lot more insight given their expertise and the fact that the latter worked on Lisp projects at Apple). Rather, I believe Apple’s departure has to do with Steve Jobs’ return. Apple was an unfocused beacon of innovation in the 1990s, with many competing visions for the future of the Mac and the company. When Steve Jobs returned, the vision became unified under him: the technical underpinnings of the Mac were based on NeXT technology, and other competing visions (such as OpenDoc) were discarded.
The saddest thing to me is that Brendan Eich didn't do a scheme like he intended.
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.
I don't agree with this counterfactual, much as I love Scheme among languages I've barely used in my career.
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.
I guess I'll start by saying that JS is my absolute favorite among all the top languages out there (StandardML, Rust, Scheme, and CL probably being the others in my top 5). Thanks for making it!
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.
It's fun to argue what-if's, up to a point. But I see a couple of factual problems:
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:
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.
Bignums 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.
I've written a great deal of Common Lisp and the only "easy things hard" I've encountered are fast matrix multiplications -- because there are many ways to do this and they each have different tradeoffs, and implementing symmetric crypto algorithms that assume 32-bit word sizes. Because Lisp integers automatically grow when necessary, forcing arithmetic to stay within a 32-but limit is hard do do in pure Common Lisp efficiently. The solution is to write bottleneck routines in assembly, which the Lisp compiler I use (CCL) facilitates.
I haven't written Lisp professionally, but my impression is that one place Lisp comes short is forcing convention:
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.
The vast majority of people who want to create something that executes on a computer are not inclined to write in a language that more closely resembles parse trees than it does human language. To many, Lisp is nearly as inscrutable as an intentionally esoteric language.
It is kind of tricky to read and probably a bit part of Python's success is down to it being maybe the easiest to read, especially for people who are not very techy as it reads kind of like English.
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).
> No version of Julia could even compile the example of the Julia book I bought, nor some of its own tutoials.
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.
338 comments
[ 4.5 ms ] story [ 290 ms ] threadCL 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.
And that’s a good thing.
I also can’t see how that would make it more popular.
- 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.
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.)
CL doesn't have opinions, it's a toolbox.
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.
https://stevelosh.com/blog/2018/08/a-road-to-common-lisp/
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.
Similarly, there's a start (heh) on a condition and restart system[1]. Julia macros are actually full-strength and that makes up for a lot. I would say that "less mature" is a fair comparison with CL but "less rich" is more of a judgement call. When people are in a mood to be critical of Common Lisp, the richness of the package/system ecosystem is a frequent target of that critique.
Binaries which aren't enormous is a main focus of development now, as I understand the timeline 1.11 will bring modest improvements (in the next few months) and 1.12 will be relentlessly focused on achieving this, it's a recognized problem.
I find Julia completely suitable for general-purpose programming, right now. Moreover I see it on the right trajectory. My hope is that when the trigger is pulled on 2.0, this will include a standardization process, resulting in an actually-stable language by about 2030. Note that Julia is closer to Rust-stable than Python-stable already, post-1.0 code which runs on the latest version (1.10) is more common than not, but I do think compatibility needs to be broken, once, eventually, to fix some bad decisions. It shouldn't have to happen twice.
[0]: https://juliahub.com/ui/Search?q=pattern+matching&type=packa...
[1]: https://news.ycombinator.com/item?id=39377229
Depends on what you mean by "image-based". Julia doesn't support reloading core dumps, but it has pre-compilation, and it has Revise, which will do everything it can to keep the REPL state up-to-date with changes to the source code. My experience is that it always updates state (silently) or fails and tells you, and when it fails can be predicted: if you modify the layout of a struct, or change an enum, it can't track that, so the prompt will turn yellow and you'll have to restart. You get the same yellow prompt (instead of green, and this is configurable to use textual cues btw) if there's a syntax error, but in that case, you fix the code and press enter and it goes green again.
Revise evals the minimum amount of code to update the program state, this is instantaneous for all practical purposes.
The Julia REPL is a massive improvement over any of the REPLs which ship with open-source Common Lisps, and head and shoulders above what comes with most other languages. It is not yet up to the standard of a commercial Lisp, or of the Emacs SLIME-mode approach, but the community lives in the REPL (and notebooks), so I expect to see the polishing process continue.
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
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.
It's so great and new that it's not compatible with last week's code...
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.
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.
That's genuinely fascinating for me, because I find postfix notation to be more intuitive and easy to parse than infix notation.
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.
That is probably much more related to why we use prefix/infix/postfix notation: because it maps to our native languages better.
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..."
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.
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.
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.
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.
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
This leads some people to assert (with other reasons too) that Clojure is not in fact a "lisp."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:
Another Common Lisp example, we'll stick to your one liner format, using infix syntax via a reader macro: 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...You don't have to, because you don't end up with }}}}}}}}} in Rust, because statements end with ; and not }.
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:
https://rosettacode.org/wiki/Archimedean_spiral#Common_Lisp
https://rosettacode.org/wiki/Archimedean_spiral#Rust
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.
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.
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.
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.
> copy list
Almost all functions have a copying and destructive variant.
hmm... maybe I need to look at the packages that use common lisp.
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.
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. :)
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.
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.
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?
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.
https://discourse.julialang.org/t/ann-package-conditions-jl/...
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.
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.
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.
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.
I think that's why Python beat Ruby. :p
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.
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.
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.
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...
The core software was still Common Lisp, but lots of cruft got added, and Conway's Law kicked in mightily.
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.
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.
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.
- 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.
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.
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.
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.
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.
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).
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.
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."
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 no...
When I said it is kind of the anti-Lisp, I wasn't thinking of it as credit...
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.
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.
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.
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.
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)
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...
Perhaps different people are inherently more or less compatible with different languages?
I think this is true. Peoples' minds work differently; different languages fit different minds better.
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!
More recently, I've felt that way about Python, and about a few chunks of my favorite blobs of self-written C++.
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?
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.
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.
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.
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.
Quoth the website*:
> New SBCL versions are usually released at the end of each month
Also, the only BSD which has even in the single digits of market share, Darwin, is only 1 point release behind.
* <https://sbcl.org/all-news.html>
I think a better example would be, the amount of embedded/appliances that ship forks of FreeBSD, OpenBSD, or NetBSD. Think of like routers. While Linux is the 1,000 pound gorilla in server space, there is still A LOT of BSD servers also. The person dimising BSD usage is only taking into account desktop share not realizing some OSs run on more than just consumer devices.
* a runtime in-memory native code compiler (-> COMPILE)
* a file native code compiler (-> COMPILE-FILE)
* and a runtime native code loader (-> LOAD)
Apple forbids providing compiled languages on (I'm talking about "ON THE DEVICE") iOS. Good luck getting those onto iOS. Their own Javascript runtime JIT is allowed, others are not. Common Lisp runtime native compilers are not. Thus one can't move a development Lisp, which includes (!) a native compiler, onto an iOS device and bring that into the Apple iOS/iPadOS/... Appstore. The Lisps which run on iOS (ECL, LispWorks), are compiled outside of iOS. They may include some kind of interpreter (source or bytecode), without a native code compiler component.
Thus: Other languages are not allowed (by Apple) to compile code to native code on (!) an iOS device. On can compile code outside of iOS and move the code to iOS (as a developer). But one can't use a language which compiles to native code ON the device.
There are other restrictions. LispWorks for example runs on iOS, but provides a special runtime with less capabilities than on macOS, also with a different garbage collector. Without the runtime native code compiler.
There might be some BSD in iOS, but different & less form an actually BSD.
To bring SBCL to iOS one thus would need to remove runtime native code compilation/loading and provide a specialized runtime with a different garbage collector. Then this would be needed to be integrated into some other application as a library, or one would need to implement a new GUI for it...
That's what I said, yes.
Although as long as you're not distributing the code these restrictions don't apply. Depending on what you're doing that's either a technicality or that's how you intend to proceed, there's a reasonable amount of iOS code out there which pays no attention to Apple's rules whatsoever, and which you can't download binaries from the App Store of.
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.
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.
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.
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.
"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.
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.
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.
One more true believer:
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.
Bignums 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.
(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 ;)
This industry will mostly use whatever Google/Microsoft/Meta sponsors.
Just look at this picture:
https://www.semanticscholar.org/paper/The-programming-langua...
We call that "line noise".
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.
clojure/cjs/babashka, fennel, janet, racket, etc make more sense to learn.