What I like about this article is that it walks through the different "camps" of Lisp. Scheme is so intriguing to me because of how small it can actually be. I can build nearly any paradigm I want to exist. The problem is, if I were to actually go find a job where they were using a Lisp, (I hear those actually exist) they wouldn't want to use my "Result monad + match statement - railway pattern" that I've used from OCaml and Rust. So learning something that is truly "common" can make more sense.
As far as learning though, Scheme feels "just right". I've imposed a "no AI until I've found a working solution" rule that keeps my mind engaged. Couple that with a willingness to say, "I don't know that right now... I'll think about it throughout the day and maybe by this evening I'll have an answer".
If you want a Scheme with batteries included, I recommend GNU Guile. Also worth your time are Racket, Clojure, Janet.
Writing scripts using [0] Babashka is also really nice.
Now there are of course limitations to what you can do in terms of not supporting Java reflection or the full Clojure compiler. But I've made some nifty small scripts and convenience helpers with it. And the dev experience of making these scripts is so much nicer than trying to write bash scripts. The Clojure edn syntax is super simple, and the REPL connected editor let me rapidly test parts of the code just like with full Clojure apps.
I don't have experience with other lisps, but I can vouch for Clojure being very nice. The community was welcoming and friendly to newcomers when I started learning, I hope it still is. One thing I love about the Clojure ecosystem and community is the effort taken to never break libraries. I've looked at libraries I used some ten years ago, and the API is still compatible with code I wrote back then. There is very little churn. Maybe this is because the language is largely untyped and editors only partially check "types". Having breakages in libraries you consume once every couple of months would get really tiring in Clojure land. I'd imagine the same problems would present themselves in Common Lisp and others.
What's that supposed to mean? Many (probably most if we only consider the non-toy ones) lisp implementations are "native" (compiling to native machine code, not interpreted).
I've already got enough of JVM compatibility to run Ring apps, and have some fun libraries like a Reagent style library on top of GTK https://yogthos.net/posts/2026-07-02-jolt.html
I've used it a tiny bit at work (on Windows) and at home (on Linux), and ran into one issue with "out" parameters, but otherwise it works really well.
I had used C++ for several years to make shareware games, so I took a test to challenge some programming courses. I vaguely recall doing well, but my advisor encouraged me to take them anyway. I'm glad that I did, because I had little understanding of theory.
Funny story: the instructor never mentioned that we could use more than one line of code. So every single piece of homework that I handed in, and every test, was one giant line of nested logic. Which worked better than one might expect, and completely changed how I wrote code from that point forward. That's how I made the connection a decade later that functional programming is akin to a spreadsheet, as are higher-order method chains and immutable variables.
I think of Clojure as being a layer above Lisp, sort of like how Swift might be considered a layer above Objective-C/Smalltalk. However, bare Lisp has problems around not quite giving enough out of the box. It's minimalist enough that developers end up reinventing the wheel for things that should probably be provided by a layer/library similar to Scheme or Clojure.
To digress, I feel that mutable variables and even monads are a code smell in functional programming since they can cause impurity. They're more of a crutch to ease conversion of code from imperative languages. However, monads can be useful to simulate every path through a program, sort of like superposition in quantum mechanics and SAT solvers. So they aren't necessarily bad, just taught incorrectly, probably because they're so hard to grok.
I'd vote to settle on a series of layers like Common Lisp -> Scheme/Racket -> Clojure/Elisp, with the final layer providing the intersection of features available from the most widely-used Lisp variants. Note that this is specifically to form a bridge from imperative languages, so research work might need additional DSL features brought forth from the Racket layer.
Edit: I forgot to mention that Scheme is a good fit for genetic algorithms, see books by John Koza (no affiliation). My feeling is that we haven't seen anything yet regarding what problems AI can solve, since it's having to do it the "bare hands" way with LLMs and pattern matching.
Does that exist somewhere ? I hope jank gets there. Or maybe roc will.
At this point, given the progress and expectations of using LLM, writing code is going to be purely a hobby concern, like carving wood furniture became a hobby once you have Ikea.
So we might as well use fun tools :)
- https://sr.ht/~dieggsy/whisper/
- https://dieggsy.com/json-literals.html
And could also be used to build languages, supporting more modern programming paradigms (though yes, I believe Racket does make this easier):
- https://coalton-lang.github.io/
I also might have written the Common Lisp example using reduce as well, which is in the standard library, but that's preference. Nice to have the option though:
(defun calculate (instructions)
(reduce
(lambda (result op-value)
(destructuring-bind (operation value) op-value
(case operation
(:add (+ result value))
(:subtract (- result value))
(:multiply (* result value)))))
instructions
:initial-value 0))
(calculate '((:add 5) (:multiply 3) (:subtract 4))) ;; => 11 (funcall (ecase operation (:add '+) (:subtract '-) (:multiply '*)) result value)
instead, looks funkier =)This would be my advice. Why? My own road was haphazard. Other books broaden your mind and teach you really cool tricks. This book gets you using lisp like you would say golang. But it still teaches you the lisp things and broadens your mind. Time spent choosing will be better spent reading this book. After that PAIP, On Lisp, SICP etc.
An Introduction to Programming in EMacs Lisp is also good for the first few chapters even if you don’t use emacs because you are given fundamental concepts of lisp that can be applied to the understanding of other dialects. It’s also free.
Learn you a Haskell (despite Haskell being not a flavor of list, they share similar DNA ) is great at understanding functional programming with lisp like languages.
That helped me to think about recursion, functional programming, and type driven development. After going through HTDP I was able to breeze through complex problems that were unsolvable before.
To be specific, you can work all of the examples in Graham's On Lisp in Python except for one of the last chapters where he implements continuations that really need macros -- but this is basically the async/await facility that Python already has. The other examples use macros for performance but work fine with just plain functional programming.
React with hooks is an example of that kind of system at work -- the JSX transformation is a very simple shim you can put in front of the JS compiler and the hooks themselves are the kind of trick that On Lisp teaches you how to do.
On Lisp doesn't use the kind of tree-walking macros that really are unique to Lisp. And... the techniques in the Dragon Book for writing compilers are the real magic. If looking to Lisp as an old shiny keeps you from learning how to write compilers, it is holding you back.
I think the homoiconic thing leads people astray. There's a real tension that, for performance reasons, mainstream compilers aren't extensible, for instance you might want to write a
unless(X) {...} => if(!X) {...}
control structure in a new language and for something in Java that is really a production rule in the grammar, maybe a class to represent the unless block, and a rewriting rule that gets applied to the AST. If the compiler was designed to be easily extensible that would be less code than the POM file for the project. But it's not so it isn't.Many things hold us back.
The industry has been so traumzatized by slow compiles that trading speed for extensibility doesn't sell to the people who create languages. Also once you have the sophistication to make things like parser generators you know how to get things done with the terribly unergonomic parser generators we have and don't have a lot of empathy for all the programmers out there who might be using parser generators if any of them were built as if usability matters.
It is true that, today, we have a vast array of impressive tools at our disposal, from parser combinators and generators, code generators to entire language workbenches with projectional editing capabilities. However, if one were to design a language for any reason, having a deep understanding of the expressiveness of computational models such as the lambda calculus would certainly be an "advantage" (especially for those who end up having to use the language): A Lisp/Scheme is as close to interactive lambda calculus as it gets. From there on, one can learn about implementing different evaluation strategies, scoping rules and continuations (William Byrd's "The Most Beautiful Program Ever Written" [2] also to mind).
Now, I am not sure whether homoiconicity tends to lead people astray. It is true that is it not strictly necessary to make a language extensible (e.g., Smalltalk has no macros and is very extensible due to its powerful meta-object protocol and an elegant syntax for closures), but it's still worth studying the concept. For example, writing a metaintpreter in Prolog [3] is surprisingly easy because of its homoiconicity.
[1] https://softwarepreservation.computerhistory.org/LISP/book/L...
Working with Clojure is an absolute delight. It strips down all the dogma and let's you deal with the "business logic" as if you're cooking steak using no BS ingredients - meat is meat, herbs are real, stove is hot.
Why would I ever choose bash for writing anything slightly more complex than simple redirection, when I can do things in way better fashion with babashka. Why would I wrestle a YAML CI pipeline that only fails on push, when I can drive the whole thing from a babashka task file, run each step locally in the REPL, and actually debug it?
Why would I ever deal with Lua, if I can't even format it for "readability" - no matter how I do it, it just looks darn ugly, and luafmt often makes it worse. Why, if I can just slash down dozen lines of Lua boilerplate compressing it into a three-liner Fennel macro? With Fennel, I can interactively poke through elements of my WM through Hammerspoon on Mac, and that's just bananas.
Why would I ever deal with JSON, when EDN is almost twice as compact and far more readable - I can align things and treat data as a literal table. Besides, I can group, sort, filter, slice, dice, salt & pepper that data easily, without ever leaving my trusted editor.
Why would I choose to build a web-scraper in Python, when I can use nbb driving Playwright and go through selectors interactively, directly from my editor, as if it is a devtools console. And I don't even have to restart anything, deal with state changes, etc.
How can I abandon Emacs where I can just open a scratch buffer, type some Elisp and change the behavior of my editor, my WM, my OS and even things on remote computers. No other text editing environment works the way Emacs does - nothing even comes close. It feels like playing a video game, where my controller in my editor.
Why would I write Flutter UIs in Dart, fighting the widget-tree ceremony and endless build() boilerplate, when ClojureDart lets me express the same tree as plain data and hot-reload it interactively? The layout is just nested maps and vectors.
Why would I reach for C when I need to embed a small, fast scripting layer. Text parsing alone would be a regex nightmare elsewhere.
Why would I bolt a templating engine onto HTML strings, when Hiccup makes markup just vectors - so my views compose, filter, and generate like any other data, no special templating DSL to learn
And with all sorts of different runtimes and dissimilar Lisp dialects, it still feels as if you're working with the same language. The mental overhead when switching is so negligible. While switching between just JS and TS - which are supposed to be of the "same family" - feels quite annoying. Despite the fact that I've put years into those - far longer than any Lisp I've ever used.
Sure, nothing special about Lisp at all. Except that practicing Lisp can actually make you a polyglot. You'd realize that it isn't syntax that makes a programming language, but runtime and semantics do. After years of dealing with different PLs, I lost a preference for one specific language - I'd choose the runtime best suitable for the task, and then see if I can bolt Lisp on top of it. And these days, it feels like there isn't a platform left where you can't meaningfully do things via Lisp.
The real thing with CL is that the entire language is available during parsing and macro expansion and that users can hook into these steps to influence them. No artificial limitations like you get in C++'s consteval, you can do everything and anything without having to use a crappy DSL to do it.
Also, I've never found SBCL slow to compile. Have you?
i had a phase when I was using PyPi a lot for branchy "old AI" kinds of workloads and felt it was an easy win but since then it has been either numpy or PIL or pytorch doing the heavy lifting or scripty stuff like uploading files to S3 where performance doesn't matter a lot.
I will grant that Common Lisp can be compiled to run amazingly quickly!
This is how https://github.com/marcoheisig/Petalisp#why-is-petalisp-writ... and https://github.com/numcl/specialized-function can exist.
It’s funny to me that it was critiqued for being “bloated” when now it looks like a focused minimal library.
Also, SBCL has some nice features specific to them, I'm sure it's the same for other implementations. So while there's a lot that's common between them all I find myself using a lot of platform specific functions.
I just find readability such a hurdle regardless of how long I used it. I didn't find that it ever became as natural as the other group of programming languages.
I find a procedural style of programming so much easier to reason about, both when writing and reading.
Either way, I'm really happy I took some time to learn it and use it a little at some point.
I've had the same complaints when I started. I think, realistically, every programmer who's learning Lisp after getting experience in a bunch of other languages has to deal with that. The mental overhead feels real. Yet, after a while, there's some psychological threshold - Lisp starts feeling more intuitive. At some point, there's just no turning back - nothing ever will feel again more readable than Lisp code. It's just like riding a bike. Once you "get it", there's just no way to "unget it" back.
You do have to keep up with the parentheses of course, but editor settings or extensions can make this automatic if not invisible.
I do find that most of my lisp skills carry over to JavaScript quite well while allowing me to write imperative functions more fluently.
Prog blocks are pretty good. I wonder if another DSL could be better.
Then do that.
There's nothing stopping you from using pretty much any style of programming that you like. Or mix and match. Or evolve over time.
Loops, lists, arrays, structures. Simple iteration: dotimes, dolist, loop. If those are your bread and butter, then feast! CL will happily do that. That's what I do. I just don't think "functionally" when I do CL code, I'm just not there yet, so its unnatural for me, and not what comes spewing out of my fingers when I write code.
And it's "OK".
You don't have to use the other features of the language, but they're there if you want to dip your toe into it.
With CL, also, I tend to be really wordy on variable and function names. I'm really fond of kabob-case-for-identifers.
Why prefer lisp-1 over lisp-2 or vice-versa?
(defun apply-twice (f x)
(funcall f (funcall f x)))
(apply-twice #'1+ 2)
Versus this with a lisp-1: (define (apply-twice f x)
(f (f x))
(apply-twice 1+ 2) ;; assuming 1+ is defined
But there are so many other differences between the lisps in the two categories that this probably won't be the deciding factor for most people.The classic example is, imagine you have a function with a local variable called “list”, common enough. Now imagine you invoke a macro inside that function which generates a call to the built-in “list” function - also common enough. In a Lisp-1 without hygiene that breaks - your local definition shadowed the built-in; in a Lisp-2 or hygienic Lisp-1 you’re in the clear.
Lisp 2 advocates typically make a few arguments. One is that having a separate namespace for functions makes it clearer when you are using a function vs another value. The second is that the evaluator has less work to do when examining the head of a list - it needs only look in the function environment, not the full environment.
On the first subject I must disagree - you can bind a function to a regular variable and then use that variable everywhere (except in the car of a list representing a function call), so for most positions in a set of expressions you don't really get information about whether the object being denoted is a function or not.
I suppose the second point is somewhat valid, though I suspect if you benchmarked interpreters and compilers it would barely matter. As a person who favors functional programming with a lot of combinators, I find Lisp 2 introduces a lot of pointless noise in the syntax for no reason. And I fundamentally just don't see functions as significantly different sorts of values, so I find the syntactic distinction bizarre.
Common Lisp and Racket are Lisp-2s but honestly, the namespace thing seems like a minor difference compared to all the other features that differentiate them.
Both highly recommended.
I would be happy with (neo)Vim setup as well, but that was way behind Emacs and broken when I tried.
I have not tried it, I'm an Emacs nerd.
Elisp::Emacs as AutoLISP::AutoCAD. AutoLISP was my first introduction to Lisp-style language. When I first started using it (1987) for macros in AutoCAD, I really had no idea what Lisp was. It was just a fun and easy way to automate AutoCAD.
Strange they did not make OpenSCAD in AutoLISP-style.
I have to read the manual all the time, because I never learn the weird syntax of OpenSCAD for-statement.
For instance "I'm new to Lisp, I want to try one..." is a person without a lot of background and information to make that choice. And they probably realize it and it makes them nervous about making that choice.
Warning about the issues that come with ANSI CL's frozen spec (threads/sockets/unicode/extensible sequences/gray streams/etc... as extensions with a varying amount of support with compatibility layers often available to write portable-ish code, "bolted-on" CLOS never fully integrated) and its various rust spots, not just the good points.
Mention that CL has provisions for gradual typing (with limits) which are exploited by SBCL.
Scheme, obviously, along with the same warning as CL about pain of writing portable code that interacts with the OS (does it have compatibility layers like CL?) amplified by the R6RS vs unfinished R7RS-large mess.
A few words about the build system/third-party packaging situation and alternative implementations.
https://github.com/modus-lisp/modus
Since you can't use an OS by itself, I've rounded out the Common Lisp environment with portable ssh client and server, web browser, and a bitcoin node. Framebuffer with VNC in the pipe
Lisps have many fantastic ideas, but are really hard to read. Lisp code is what we had before perl guys went "hold my beer".
I know its just syntax, and it usually does not matter, untill it does. I did some clojure a long time ago, and before that some CL, and god, i cant understand my own old code. Contrast that to some language that has syntax i can read it still, years later. Go being the prime example of write once, read a decade later.