Lisp really is an eye-opening experience when learning to structure programs and computations. Stumbling on Lisp in college was a fascinating experience for me in particular because I had been raised around C++ and Java, and my programming life up to that point had been a struggle understanding how to abstract a program into objects and how to build those objects' interfaces.
Once I started down the path of functional programming and Clojure, it became clear in retrospect that "objects" as C++ and Java envisioned them were poor fits for most problem domains, which is why their purpose never clicked in my head. Reducing data down to...well, data, and methods down to functions, and programs down to pyramids of expression calls made everything about program structure click in my head. Functional, expression-based style was FAR more intuitive to my brain than stateful object style. I now try to lean heavily into expressions and referential transparency no matter what language I use, though some languages make it easier than others.
Funnily enough, C program structure came much more naturally to me than either C++ or Java. Maybe because it's easy to decouple data from operations on that data, even if stateful. Hard to say.
A big piece of OO development is state management. If you're modeling a system having several interacting entities whose interactions may vary based off that entity's current state - then OO is most likely the way to go. You can encapsulate that state management in one place rather than having it strewn throughout the entire codebase.
If you're doing information-oriented processing, whether it be a traditional batch process, or a decision-support system reacting in real time to incoming data - then functional is most like the way to go.
If you're doing corporate CRUD development, then just use JavaScript (assuming Web CRUD) and the popular CRUD framework du jour.
This is what it means to use the right tool for the right job. There is no perfect language that excels at everything. It all depends on what you need to do.
The classic mistake most programmers made with OO is to translate the business domain objects to classes with behavior. While this allowed for some interesting cases like with Naked Objects, it was usually not the most useful way to analyze a system and encapsulate application functionality.
The better way to employ OOA&D was to divide the system functions up into classes. You have an object that manages connection pools and an object that serializes data, and an object that manages tasks... These were the more useful abstractions, because for business systems we're generally not doing simulation.
The state of the system encapsulated in these objects was the operational state of the stack, not the user state which was just more data to throw around. Abstracted this way, however, you get roughly the same kind of system over and over again. How boring! That way leads to frameworks... Which is also what you saw in the late 90s and early 2000s. It's because for most business applications the abstractions of the system, as opposed to the business domain, are so very close to the same that you can, in fact, codify those into cookie-cutter apps where only the domain data being passed around differs.
Functional programming starts you in this state of abstraction, which is why it tends to be simpler for business systems.
Of course you don't, that's why I said OO is most likely the way to go.
I also spoke of more than simple state management - If you're modeling a system having several interacting entities whose interactions may vary based off that entity's current state - isn't simple state management.
You still don't have to go OO to solve that problem, it's just a very good choice in most circumstances.
Also, inheritance is not a core aspect of OO. It's a common aspect of OO and even a popular aspect, but it's not core. To wit, there are OO systems not having inheritance.
I wouldn't say reuse of functionality is a core aspect of OO, but I will say that's a core reason why OO was so heavily adopted in the 90s. It's mind-numbing to recall how much time and money was wasted on making things "reusable" only to never be reused.
> I wouldn't say reuse of functionality is a core aspect of OO
You'll find it in practically every object-oriented language, starting with Simula. Later Smalltalk, Actors, CLOS, C++, Self, Java, Objective-C, Python, JavaScript, ...
> making things "reusable" only to never be reused.
It's similar mind-numbing how much has been reused.
Yes, there are a bunch of frameworks, which make use of inheritance and/or delegation.
Flavors (an early object-oriented system for Lisp, early 80s) provided multiple-inheritance, mixins, method combinations, etc. and a bunch of software made use of its features. Object Lisp was another OO system for Lisp, which one used for example in an interface builder, which made extensive use of delegation. The Common Lisp Object System later was designed improving on Flavors and LOOPS (from Xerox), switching away from message passing, adding multiple dispatch. Thus dispatch based on inheritance works over possibly multiple arguments.
Inheritance and delegation have a long history in Lisp going back to the 70s.
IMO inheritance is more syntactical QoL than core. That’s just code DRY stuff and composition is fine for that too. The architectural elements of OO are state machine modeling (encapsulation) and message passing.
Message passing means that objects communicate only via messages with some payload. There is a sender and a receiver. Possibly there are many receivers in a broadcast message. Messages will run synchronous or asynchronous. The receiver decides what to do with the message and how to interpret it. If it does not understand the message, it can forward the message via inheritance, delegation or similar. The message implementation belongs to the class and/or object. There is a syntax for sending messages. The mental model of a developer is based on communicating objects.
In Common Lisp the object system uses generic functions, which assemble several related methods. Generic Functions are defined outside (!) of classes and objects. Generic Functions are both CLOS objects and functions. Generic functions are called with one or more arguments. The Generic Function decides which methods are run and in what combination. There is no equivalent to a receiver: CLOS generic functions dispatch over its arguments, all primary arguments. There is no equivalent of a privileged receiver object or a self. There is no special syntax for calling a generic function and they are themselves first class objects, which can be stored and passed around. The mental model of a developer is based on function calling of runtime assembled functions.
Common Lisp is one of the few truly multi-paradigm languages.
It's annoying the standard has not evolved a bit to bring some ideas you see in Clojure, or some computation models featured in CTM & Mozart/Oz that have made it to Racket.
What killed Scala at my last employer was that nobody could agree on what idiomatic code should look like, so there were vast stylistic differences between areas. Despite working in the same language, it ended up being hard to move between codebases, in unpredictable ways.
This strikes me as the same sort of flexibility: you can do just about anything. If you do do just about anything, it's a bad time for all concerned.
Common Lisp has CLOS (Common Lisp Object System), which is quite sophisticated object system with metaobject protocol and multiple dispatch, as a part of its spec. Not that it's smth people need to invent on their own each time
Well yes. The problem isn't people building their own implementation. The problems with Scala aren't with them inventing their own type system, or pattern matching, or whatever. It's the lack of agreement on what is the idiomatic subset of features to use in a given environment.
I would think that Scala is slightly more object-oriented than Common Lisp, given that one can mostly ignore OOP in some Common Lisp applications. Thus that part already (macros !) makes CL very flexible in expressing different ways of programming. CLOS without MOP has a range of possible use. I haven't thought about how consistent typical code actually is, using CLOS without MOP. My intuition would say that there is some common CLOS style, but maybe that's wrong. It would be an interesting question to look at, how CLOS code bases make use of its features and if they share common architecture principles. Actually there is not that many literature on software engineering principles for CLOS. For example see: "CLOS in Context: The Shape of the Design Space" by Bobrow&Gabriel&White.
The MOP itself was thought to make CLOS programmable and thus to support a wide range of different object-oriented styles in a single language framework. In practice there is use of that, but it's not that common and actually also a bit complicated/tricky/challenging to program the object system in itself for different behavior.
The OO patterns are still quite popular in enterprise software, where people mostly work in large teams. Hiding implementation details is terribly annoying for a single developer, but can be a great way to reduce complexity for other team members.
The same consideration applies to other powerful constructs, such as static typing, garbage collection, operator overloading, and macros.
Whether you work alone or in a team can significantly influence how one appreciates such features.
I could certainly be wrong, but that doesn't seem to be the case to me.
When you completely separate routines from the data, you end up coupling the code that calls the routine with the data it’s passing. I don’t see how it would be possible to maintain that level of separation while achieving the same level of isolation that’s possible with objects, where it’s possible to truly know nothing about the data in a given interaction.
I’m referring here to the purest aspect, in terms of pure functions.
At some point, the data has to come from an impure source, and honestly, I don’t see why a closure would be better than an object—practically, they’re the same thing.
I know Java programmers use static-method classes, which are just like modules in newer languages. Which just further illustrates the point. It's a hack on top of a language that requires everything to be defined inside of a class. That layer of abstraction on top of things that could be much simpler kills understanding. It massively complicates answering the question "What even is a class?" for a learner. I also realize C++ has simple structs, and classes can be avoided entirely, but that's definitely not the way the language is taught.
Maybe I needed to "git gud", but learning Lisp enabled a career building valuable products currently in production. Java and C# were a roadblock on the way to my current productivity.
I didn't expect that much negativity regarding my comment, I never implied you need to "git gud".
I'm happy in my Clojure job and I would rather not work with OO languages, and that's definitely not how they're taught, but if/when you're forced to use them you can choose not to follow the traditional way and instead write them in a more functional/data oriented way.
Almost all of the code examples in the book are in JavaScript (not Java) though a significant feature of Sharvit's approach is that it decouples Data Oriented Programming from any specific language. As a Clojure geek, I highly recommend the book as the way to achieve some of Clojure's core virtues in other languages.
Sorry for going off-topic, but why is this comment being downvoted?
The comment provides an alternative view on things and even links to a nice book. One may not agree with the perspective, but that's no reason to downvote.
Perhaps it's the user interface (a special upvoting wand would be most welcome for those of us whose fingers are too fat). Or have we reached the stage where script kiddies deploy downvoting bots if they didn't like a comment in another thread?
Having programmed professionally in 36 languages, I must agree with this. I am having so much fun with Lisp, that I am not likely to go back.
> Often developers say how you can make a mess of a Common Lisp codebase because of such freedom it provides. But isn’t that the same with any language?
There is a story about the code behind the terminal interface for Tops-10 for the PDO-10. It had been tweaked over the years adding various features, such as using control-T to see what was happening in the running program. Many attempts were made to rewrite it, but it was so intertwined, that whoever tried fixing it gave up.
Three things are killer features for me with the slime+emacs running sbcl. First is that you can see the underlying source for anything down to the lisp implementation, e.g. the definition of 'macrolet'.
The second thing is if there is an error, it displays the stack with the ability to see what the local variables are in any function on the stack.
The third thing is that one can patch a running program, either once a breakpoint ('break) is encountered, or by doing control-C at the repl. And then resume it.
It is also fun that SBCL is unreasonably fast. It compiles itself in 2m15s. I am pretty sure that gcc/clang/llvm take longer than that and also likely Rust.
I have developed a habit of using s-expresions for lots of things, such as configuration files, and web page representations. I blame paredit and lisp for this tendency. See https://github.com/wglb/configuration-r
But here's the thing, and I don't know, I never worked with Genera.
But.
Genera has been gone for over 30 years. At the time, it was certainly revolutionary. But in the computing world, where things move VERY fast, whatever made Genera amazing clearly wasn't amazing enough to replicate in full anywhere else.
I'm not even talking about other languages, trying to get that dynamic environment in modern languages, with modern tooling (which has made enormous strides over the years). But not even in the Lisp world has it resurfaced. Nobody is talking about how amazing Franz Lisp is, or LispWorks, both long running, successful Common Lisp implementations. Run by smart people, run by people "who were there".
We have Slime, with Emacs. We've had Slime forever. We've had solid Common Lisp implementations...forever! People still lament how good the Macintosh Lisp environment was (and honestly, I don't know how, or what made it more than what Slime might offer beyond nice access to the Mac toolbox).
Do you know what has changed in modern Common Lisp implementations since Genera? NOTHING! It's Common Lisp! Common Lisp is 30 years old, and a superset of whatever Lisp Genera was running. And you can't tell me that we don't have modern Genera because Flavors (Genera's object system) is so amazing and CLOS is lacking (because even if it was, it's Common Lisp -- you can FIX IT!, or simply implement Flavors because it's written in LISP!).
There are no secrets here.
Veterans speak of Genera in hushed tones. Sing songs about it. But, however good it was, it's, demonstrably, not good enough to do it again.
Meanwhile, there's been a dedicated pack of folks over at the Pharo side making Smalltalk even more Smalltalk with lots of new development tooling. These are folks that write Pharo so that they can make writing more Pharo even better. I swear, I think they're to the point that they just mount GIT as a file system now over there. I'm not a Smalltalk guy, but, boy, those folks have been riding a rocket for years.
So, it's not lost. It's abandoned. Left behind. For whatever reason. There's a difference.
Another thing is, that despite Genera having been gone from the real world for over 30 years, it is still nonfree, and most of the interesting software that ran on it hasn't been publically archived either. That has made it essentially worthless to most people who would otherwise be interested in it. On the contrary, the MIT Lisp Machine system from which Genera was derived is completely free, as well as the LMI system which was another derivative. And there are a small handful of enthusiasts who continue to use, share and improve them.
> Another thing is, that despite Genera having been gone from the real world for over 30 years, it is still nonfree
Eh. That's a non-starter. The code may be closed, the concepts aren't. We've been reinventing the wheel for ages, and the Genera developer experience apparently isn't novel enough to be worthy of being reinvented.
My point is that for people who wish to experience a Lisp Machine system for what it is, and explore its unique concepts, they have free systems they could be looking to rather than a dead proprietary one.
> I don't know how, or what made it more than what Slime might offer beyond nice access to the Mac toolbox).
GNU Emacs plus SLIME is a remote development environment for an external Common Lisp, using a text-editor-based environment.
Macintosh Common Lisp was an integrated GUI-based Common Lisp develop environment written in itself.
Those are two very different things.
> Flavors (Genera's object system)
CLOS and Flavors are both Genera's object systems. CLOS was actually co-developed by Symbolics. The core designers were Keene & Moon from Symbolics Inc., Kiczales & Bobrow from Xerox PARC. and DeMichiel & Gabriel from Lucid Inc.
> Three things are killer features for me with the slime+emacs running sbcl. First is that you can see the underlying source for anything down to the lisp implementation, e.g. the definition of 'macrolet'.
If you’re working on a project (on its code), then you can (obviously) see its code as well.
Statically typed languages make exploring the code a lot easier, especially with an IDE which leverages the type information to assist in code exploration.
> The second thing is if there is an error, it displays the stack with the ability to see what the local variables are in any function on the stack.
This works with debuggers (and is easy with an IDE) in most popular languages.
> The third thing is that one can patch a running program, either once a breakpoint ('break) is encountered, or by doing control-C at the repl. And then resume it.
You can do this with most major languages today as well.
These three things are all done from the REPL. I don't have any experience doing C or C++ from a REPL.
I disagree that "Statically typed languages make exploring the code a lot easier." I'm not aware of an equivalent in C or C++ to the "esc-." method of going directly to the source.
The keystroke combination to find the definition of a function or symbol using slime and emacs is "esc-." That takes you right to the code. If it is a symbol defined by Lisp, you see the underlying code in the actual source for Lisp.
So the second thing is also done when running a program from the REPL. Once tripped, you can expand each element in the stack to see and inspect the variables local to that function. You can then edit the source for that function, compile only that function in place, then resume execution. I admin that I don't know how to do that in C or C++.
My explanation should have referenced the REPL, which is key to these.
> Once tripped, you can expand each element in the stack to see and inspect the variables local to that function.
When a breakpoint is tripped, with most modern IDEs and languages, you can see the local stack, global state (and pretty much all state), and this state is presented in a UI with dropdowns, etc. Debuggers in modern IDEs also let you evaluate an expression in place, where the breakpoint was tripped, so you can further dig into the local state with a debugger. This sort of works like a REPL (but you're "sort of" limited to a single expression -- but that's something you can still circumvent with an immediately-called lambda).
>You can then edit the source for that function, compile only that function in place, then resume execution.
This is supported by some languages, but admittedly support for this varies widely, and sometimes can be weak or finicky. For example, the JVM family of languages have a feature called HotSwap. This feature is quite old: https://www.jrebel.com/blog/java-hotswap-guide -- it was introduced in 2002 with the Java 1.4 JVM. I use it even today, with personal projects, where I update the code of a running server live (without restarting the server).
HotSwap unfortunately sometimes is broken with certain major JVM frameworks, but for my personal projects, I've always made sure to only use libraries that work well with HotSwap, since I consider live code patching of a running server to be an absolutely important feature.
>the "esc-." method of going directly to the source
To be honest, I'm not entirely sure what you mean here. When I'm working on a project (as in, I have an IDE open for it, and I'm writing the code for it), all the code is right there. When I use a debugger, I can see the stack of all the ancestral functions, and with a single click I can go to the definition of any function. Static typing basically makes the "Go to definition" more precise, as it always jumps to the place in the code (file & line) where any particular function or value is defined. With dynamically typed languages, an IDE can sometimes show you multiple option when there's multiple functions with the same name defined in different places.
>These three things are all done from the REPL. I don't have any experience doing C or C++ from a REPL.
Most of the features I've described above work with C and C++ IDEs (like CLion, Visual Studio, etc), except for the live code hot swapping -- I've only used live code patching with JVM HotSwap with Java, Kotlin, etc.
> Most of the features I've described above work with C and C++ IDEs
In Common Lisp you won't need an IDE and you won't need to instrument the application with a debugger, because much of the features are built into the language runtime.
For example Common Lisp has an error handling system with resumeable exceptions.
Another example is the object system where the objects are updated on changes. For example I can load a new class description and the existing objects are changed to the new description: new slots, fewer slots, new superclass, removed superclass, ... This makes hot loading much more useful. Additional many calls to global functions are late bound, which again makes hot loading more useful.
A typical runtime also includes a code loader, a compiler and/or an interpreter, which makes updating code easy. All without an IDE or a foreign debugger.
This also means that when for example the resident compiler is called, the compiler is a part of the runtime. An error under compilation, will show me the compiler in the stack trace and the compiler itself can be live edited/updated, while I'm in a compilation error.
The effect is then, that by default programs are running inside a runtime, which always provides the features of interpretation, compilation, code loading, repls, debug repls, runtime type checks, OOP updates, ... One would need to do something to turn those things off.
In a typical C/C++ program, the compiler, debugger, IDE, linker, ... are not a part of the program's runtime.
All of this late binding, and needing a runtime, seems to suggest even a JIT LISP implementation (e.g. SBCL) might not be able to achieve performance similar to a "zero-cost abstraction" language like Rust. When you're in debug mode (e.g. with C++ or Rust or a JVM language), there's a bunch of debug info added to the binary plus various optimizations are turned off. I'm sure some optimizations that C++ / Rust / LLVM use involve entire eliding classes and other abstractions, so that an "O3" release-mode binary doesn't even contain any cruft. There's an argument to be made for this approach, at least for software that's run on customers' computer. For server side software, I can see why having always-available debuggability is useful.
> might not be able to achieve performance similar to a "zero-cost abstraction" language like Rust
That might be the case, even though SBCL might have some performance improving features, like a runtime compiler (which can be used to improve performance at runtime) and the capability to implement assembler extensions to the compiler from Lisp.
> There's an argument to be made for this approach, at least for software that's run on customers' computer. For server side software, I can see why having always-available debuggability is useful.
I see no reason, why this should not be useful on the client side, too. Many software systems on the client side have ways to extend them. Doing this in the main implementation language can have advantages, instead of doing it in a limited macro or scripting language.
Practical Common Lisp is also a nice read, and the Cookbook* seems to be coming along quite nicely. There's also CLiki (the Common Lisp wiki), but I'm not sure how up-to-date it is.
This is exactly why I exclusively write Perl, C, and Lisp, simply they are fun to write, and provide me with the capabilities to feel powerful as a developer. Most software written these days lands in languages that make me feel like a drone, endlessly fighting a system in place or a pattern for some arbitrary theoretical gain that is never realized in the real world anyways, OOP, Type-Systems, FP, whatever, why would I willingly commit myself to someone else's idea of what code should look like?
On side note, they say that 80% of developers are unhappy, do we think that languages have a big part in this?
C, Perl, and Lisp? Amazing, it's like I'm looking into a mirror: those are the 3 languages I appreciate the most. The first time I tried coding something pseudo-practical in C (a simple program which counted the instances of every alphanumeric character in a text file) it ran so fast I had to do a double-take. Perl was the first language I decided to learn for fun and coming off Java it was nice not needing pages and pages of boilerplate to do simple stuff.
Common Lisp is very close to being my ideal language. It would be neat if I could do something like...
(defstruct (Kons (:generic A D))
(kar :type A)
(kdr :type D))
(deftype Lyst (&optional e)
`(or Null
(Kons ,(or e t)
(Lyst ,e))))
(deftype Character-Lyst ()
'(Lyst Character))
I think that these practices are almost always tied to languages though. For instance I've never worked at a Java shop that wasn't an overly corporate, manager oriented, scrum/agile mess. Whereas all of the Perl shops have been the extreme opposite. Could be just my experiences, but I see a correlation personally.
- Perl: prototype to C, short things to not spawn awk/sed/sh, or silly things from CPAN
- C: Sadly, C is to computers what English is to science/tech/world: maybe not the fanciest or easiest language, but it's everywhere on low level business and everyone being educated has at least basic competences.
- CL: Crazy things you wont see IRL until 20 years have passed from now.
I do web dev now, but it reminded me of good times coding in Julia.
Like I once wanted to have my own syntax for querying a SQLite, so built my own ORM. The query syntax is defined in like 50 lines of code. Doc for it here [1]
I can relate. I read Graham's book on Common Lisp more than 20 years ago and was entranced.
But opportunities to use it in the day job are few and far between, and I would have qualms about recommending it, in most contexts. For a lot of purposes, its libraries are just nowhere near as good as Python or JavaScript or Java. And some of its superpowers (macros!) can also be used really, really badly.
There was an essay about Lisp being a "language for smart people" that captured some of what would give me pause. On average, most shops have average people (for that occupation/industry). Not sure Lisp is the best solution for that context.
I write Go for a living. I run Emacs. I read the first part of the article, thinking about how Lisp used in a personal project makes so much sense because you can build the meta-language to your liking and not make something so weird you confuse your coworkers --
and then I got to the section on Go and I thought wow! This guy really doesn't get it!
>On average, most shops have average people
Yes, hello, that's me! I don't want to figure out some genius's Lisp code. Instead, Go forces the geniuses to write code that I can understand.
>There was an essay about Lisp being a "language for smart people" that captured some of what would give me pause
I think about this a LOT. There is a LOT of this blindness in software philosophical thinking. Just look at FOSS: everyone should be able to view and edit source code. Well that sure made a lot of sense when "everyone" was MIT students because they were Richard Stallman's audience.
But it stops making sense to expect users to inspect source code when users aren't all MIT students. Likewise, use your smart person language on your personal project -- I'm sure it's great. I'll keep using the dumb people language.
And get replaced by an LLM, I guess. But if an LLM can generate Go code (like the author surmises) and not Lisp then it might as well just emit machine code and put us all out of work so I don't know why he thinks Lisp is a moat
I think that the idea that Lisp is for smart people is misguided and wrong.
I don't think a person needs to be a genius to do well in Lisp. As a long-time C and Fortran and Python programmer (and many other languages) it took a long time for me to adjust to the different way of thinking necessitated by Lisp.
Also if Copilot or LLM write Go code, why do we think it can't write Lisp code?
I've dealt with code in many languages that were written by "geniuses" and find that they can be as hard to understand as any Lisp program. Often this depends on the problem being solved. There is a story about Donald Knuth and Edsger Dysktra working on a problem that required no less than four stacks to process. (If memory serves correctly, they found a solution using iteration rather than stacks that was much clearer to understand.)
The solution of Sudoku by Peter Norvig (https://norvig.com/sudoku.html) is a bit of a genius solution but it is written in Python.
It would be a disservice if the article's distinction of smart vs average were to dissuade anyone from learning a new language.
> I think that the idea that Lisp is for smart people is misguided and wrong.
I think it's a misunderstanding. People think that other people are like them (and in particular, think like them).
I think that some languages are a better fit for how some people think. Other languages are a better fit for how other people think. It's not "smart vs. dumb", it's different thinking styles.
Now, if FP (or at least non-procedural) fits your thinking style better, and all you've known is procedural or OO languages, when you run across Lisp it's this amazing, magical experience. You never knew programming could be like this. It's enlightening and freeing. It just fits.
But when you try to tell someone else, and they say, "Eh, Lisp never really clicked for me", you think: "How can it 'not click'? It's this wonderful, magical language!" It's really easy to assume "they're stupid" instead of "they think differently".
One idea of using Lisp is to learn new thinking styles for programming. Thinking styles are not fixed. For example an early hurdle in programming / computer science is recursion. Lisp gave the teachers a tool to teach thinking of software development in terms of programming with recursive functions&datastructures, starting with linked lists and operations for it...
Unfortunately students often got the impression that Lisp is only good for learning new ways of thinking (in combination with a theory of recursive functions) and not for actual software development.
>>> There was an essay about Lisp being a "language for smart people"
Boy! Nothing can be further from the truth! At least my experience, trying to teach people to program in java, c, c++, lisp and assembly to both technical and non technical people, the only language that is easy to teach is lisp. By far the only language that everybody “got” and could actually do something on it.
Compare that to c++, where experienced programmers shoot themselves the feet every single day.
I think the essay had less to do with syntax than appreciating what makes lisp special. But sure. I love Lisp's ultra-simple syntax. And it makes macros work, which is indeed a superpower.
I'm a big fan of lisps, and haven't played sufficiently with rust to have an opinion -- but I will note that the benefits of a sophisticated type system is qualitatively different from, say, the statically-typed environment of C or Objective-C. The comparison I make when thinking about CL is something like Haskell or Elm, which /do/ have that feel of "if it compiles, it works". Without that, I do have the feeling of working without a safety-net that pushes against that sense of freedom that dynamically-typed languages like CL otherwise offer.
(Obligatory mention here of Coalton, which is an experiment in creating that sense of security within a CL paradigm: https://github.com/coalton-lang/coalton )
I've never managed to achieve that (the comparable sense of security), which may be on me.
Out of interest, have you worked in one of these typed languages -- Haskell, Elm, maybe OCaml (haven't tried it), Idris, Rust? I think they feel very different in this axis compared to C/C++/Objective-C/Java. Not necessarily better (I am still reminded of the term "bondage-and-discipline language" from the Jargon File[1]), but different.
I like CL as much as the average dev who's dabbled with it on weekends but can't find anyone to pay me to use it, but...this essay really adds so little to the conversation about it. There's not an awful lot explicitly wrong in it per se, but people have been writing these little puff pieces on their blogs about Lisp for (at least) twenty years now. This checks all the usual boxes: "it's so powerful and liberating", "working in an image on a living system is amazing once you try it you'll see", "critics are wrong you can write unmaintainable code in any language", etc.
OP hasn't even really used Lisp in anger, yet: "my next project is being built with it". I'd be much more interested in seeing a postmortem after that - "Ten Years Building a Major Piece of Software in Lisp" - with some actual self-reflection, rather than just a regurgitation of the usual talking points.
Blog post author here: True. But there's no reason not to write when you are in anger about other languages that you do use every single day.
I guess I was just venting and also reaffirming my intention to use CL. I have used it before in a previous job for a quick tool and I know it can be used for more complex stuff, which I have already started building.
I haven't been writing a major piece of software in Lisp for 10 years, so I can't write about that. But I can compare how it feels to write software in other languages vs Common Lisp.
And also, Lisp is a blip compared to Rust or Go right now, so I don't know why it's so terrible to talk about it. People will keep talking about the things they like as much as the things they don't like. And it's fine.
> OP hasn't even really used Lisp in anger, yet: "my next project is being built with it". I'd be much more interested in seeing a postmortem after that - "A Reflection of Ten Years Building a Major Piece of Software in Lisp" - with some actual self-reflection, rather than just a regurgitation of the usual talking points.
I should probably write that post, though about "a Lisp", not strictly about Common Lisp. I've been using Clojure to make a living for the last 10 years or so, quite successfully and I can't see anything else that would fit my needs better. I came to Clojure from Common Lisp.
This is a good perspective. I did work in lisp, including production systems (albeit small systems), for many years and authored many open source projects in CL. I'd say the biggest hurdle was the community. There was a strange balance between getting contributors and people going off and rewriting the thing you're working on becuase they can do it better/make it faster/etc. It definitely skews towards people going lone-wolf. A lot of people said this going in but I ignored it because I love the language (still do) but eventually got burned out on trying to do anything real with CL. I absolutely did get highly impactful contributors on some of my projects, and I'm still thankful to them, but there was also some "this isn't fast enough I'm going to just rewrite it" that frustrated me a lot.
One place I think CL absolutely would shine is game dev. I started using it for that a bit and the ability to redefine the system while it's running is an incredible superpower. Forget compile times, rebuilding the state of a game can take a long time and being able to fix a bug or adjust behavior right then and there is really cool. This probably isn't even novel anymore given modern game engines, but at the time I loved it.
I enjoyed using elisp and common lisp for the years that I spent in emacs and stumpwm but it always made me chuckle when I'd read some comment or blog post proclaiming that learning lisp is somehow like ascending to a higher plane of developer consciousness. I feel like that sentiment was really high around the late 00's and early 10's for some reason, but also it may have been because that's when I used it most.
I guess I need to write that then. It's not magic, it's not all powerful etc, but, for me/us, it is better than anything else that is there. Our company library is basically a dsl that grew over decades and it is a pleasure. I work with other tech and it basically really sucks balls compared to CL/SBCL/emacs. I am sorry for anyone who missed out on this, but it is not a religion; use what works for you.
That could be easily be my post a couple of years ago.
Let me give me my post-vitae:
It was great. I had to teach Lisp to a dozen people, convince them it was a good idea. Right now we have many systems ans thousands of lines in code in Lisp. Everybody is loving it. From management, amazed with the speed of development, and fewer bugs, to the test, ci/cd people having much less trouble.
This struck me, too. My current hobby project is in Go and I'm really quite enjoying it. It doesn't feel "soulless" to me at all. That said, I'm well aware that my opinion may be different if I had to write Go for a living instead of just for fun.
I've also been dabbling in Go recently, and I'm finding that the culture surrounding it is one of actually understanding what your code does. My main language is Ruby. It hits many of the same high notes that Lisp programmers talk about, and I enjoy writing it. There's just something about the practical, concrete nature of Go code that appeals to me.
I use Go too; I am an old guy and I like C; go reminds me of it. I don't have to remember anything; I can just dev with it without internet if need be. But I can also do that with CL and then, well, everything is better. For me anyway.
It really echoes my first memories of personal computers.
You powered it on, and it landed you in a BASIC environment. Apple IIs, C64’s and Ataris all had a full-screen editor environment where you could type code, run it, pause it, inspect variables, and continue running. It wasn’t LISP, sadly, but it was great as an introduction and moving to “more professional” environments and tools took away a great deal of freedom away from us.
Am I the only one who just bounces off the ecosystem? I don't mean emacs/slime/sbcl, it's once you get beyond that.
In my most recent foray I wanted to give roswell, qlot, and quicklisp a proper try, because I really want to find the Right Way to consume dependencies with proper version management. It's what I'm used to in just about every other context. But the version of qlot you get through roswell is old, nothing in that stack is doing versioned dependency resolution, and despite claims of stability, SBCL and ASDF seem to have had incompatibilities recently that'll trip you up if you happen to install the wrong thing, so you don't get Clojure's "code from a decade ago just works" effect.
What have I missed? It feels like I'm holding it wrong.
In this comment, I am a common-lisp noob, no competency at all. I have used and written a lot of sw in a lot of languages over 40 years though.
Keeping in mind the concepts of per-user and per-project installations, I have done the following experiments:
1) Quicklisp only. Not beating on Zach but we're getting close to a year
w/o updates, so something like cl-transducers needs to be installed from upstream.
Upshot is, other than the lack of updates, adding packages works perfectly. But I am curious how to delete a package. Installation is per-user, although stuffing packages into the asdf visible directories give some customization ability. (I think?)
2) The whole shebang: ros + ros-quicklisp + qlot. I spent a few weeks with this combo, digging around the code and installation structure, and... I have no idea how to manage this thing down the road. Seems to work, but there's a lot of assumptions about per-project vs per-user package installation. AFAICT, ros packaging means per-project quicklisp, which is fine, but no versioning. So then add qlot. Now we have versioning, which is great, but how do ros installed quicklisp packages interact with qlot? Especially since qlot add is a thing.
3) In order to simplify the system (ahem) I decided to follow the hints in the qlot installation section and simply install qlot with no quicklisp or ros on a clean system (ahem). I also reinstalled sly from clean and found out that I had to install sly-quicklisp in order for it to work. That was yesterday and I'm still scratching my head about what kind of a beast I am working with. What part is quicklisp and what part is qlot? I am also working a lot closer to asdf, experimenting at each step. Learning a lot, but, I'm not really getting a lot of happiness out of wading through at the source code level all these overlapping packaging systems (ahem). If I persevere I suspect I'll know a lot about the current state-of-the-art of lisp library packaging.
I am right now contemplating clearing the deck once again and doing the 2) ros + ros-quicklisp + qlot again, because it seem to work fairly smoothly and provides the opportunity for per-project versioning. I'll have to defer the understanding part into the future, though.
I play around with quicklisp to develop and tinker.
To test and deploy, I generally use Guix [0] to express a package for my application which pins dependencies to exact versions and optionally can run in a container. I have Guix installed on top of Ubuntu.
This is hobbyist and experimental work so it's not battle-tested.
Yes. I used Clozure CL before and now I use Lem as editor. But I stick to SBCL (at least for now, though I can see myself using CCL again when it supports ARM64).
As for LispWorks, I installed the Personal edition and played with it. I can't afford it (actually I can, but I have to find a reason other than tinkering).
A PG article about "doing what you love" was recently shared and discussed on HN [1], and I think a lot of the same logic applies. The nature of a job is that someone is paying you to do what they want you to do. If we're talking about what makes you happy in your free time, then sure, learn any obscure language that appeals to you. If you find something cool that you can do in CL and nowhere else, that's an amazing opportunity. If you just happen to think that life is too short to use a practical, established language, then I don't want career advice from you.
I didn't give any career advice at all. Why did you think this was it? My career advice would be to keep developing in whatever established and practical language you are using and get paid to use if that's what you want.
Ah, then I accept your advice. I happen to like programming, but I also view it as a practical skill and a means to an end. That's the lens through which I was viewing your post. If your central point was that you personally enjoy Lisp, then I don't have an opinion one way or the other.
I also view it a as practical skill. I'm paid to write in a practical language which is mutating into a monster I no longer consider fun to write in.
But as I started a new project which eventually could be a business, then I can use whatever language I want, as long as it's practical for my use case. Why would I use any other language than one that I already know and like? It would make no sense. If you were to start your project, you would probably pick a language you enjoy using, rather than the one you are paid to use, unless it happens to be the same.
> How long before Copilot is able to write all the code any Go developer now writes by hand?
A long time ago I read someone saying "Lisp is the only language where I spend more time thinking than typing". Recently, when I was telling ChatGPT what logic I wanted written on what data structures, I realized the same was true about having an LLM write any other language.
Honestly, to me that sounds like a red flag. There are already too many temptations to get on a purist mood and spend ages thinking about elegant code structure.
I know, it's ambiguous, "more time thinking than typing" might imply that you spend more time thinking about the core problem you are solving. But let's be real, it's Lisp, you are thinking about how to model your problem elegantly in Lisp, not about how to solve it.
This is incidental complexity, you might feel mightily smart and productive, but you are really not getting anything substantial done. Clean code is critical, but in moderation, you need to constantly prevent it from upstaging the actual problem you are working on.
Part of the Common Lisp experience is tool building at the language level.
You could be digging a hole with your hands. But thinking a moment, somebody came up with the idea of a shovel. That made a huge difference.
Similar, tool building & usage at the language level can also make a huge difference. In larger applications this makes code much more compact and makes a person more productive.
One minor point mentioned: I find it odd how many great PL designers I’ve known are hostile to learnings from other languages. Maybe because they field a ton of naive “why can’t A do what B does”. But languages are similar in many respects, and there are a great many lessons to share across.
Once I started down the path of functional programming and Clojure, it became clear in retrospect that "objects" as C++ and Java envisioned them were poor fits for most problem domains, which is why their purpose never clicked in my head. Reducing data down to...well, data, and methods down to functions, and programs down to pyramids of expression calls made everything about program structure click in my head. Functional, expression-based style was FAR more intuitive to my brain than stateful object style. I now try to lean heavily into expressions and referential transparency no matter what language I use, though some languages make it easier than others.
Funnily enough, C program structure came much more naturally to me than either C++ or Java. Maybe because it's easy to decouple data from operations on that data, even if stateful. Hard to say.
reply