Skip to content(if available)orjump to list(if available)

Why I Chose Common Lisp

Why I Chose Common Lisp

202 comments

·January 12, 2025

agentkilo

SBCL is a great choice! It's a surprisingly dynamic system (so are other CL implementations).

A while ago, I did some private work for someone, using SBCL, and sent her the native binary I built, then forgot about the whole thing.

The client came back with some new requirements much later, when the project was concluded, and the source code was already lost by that time.

I vaguely remembered how I did things, so I spawned a REPL from the old binary, went into the relevant CL package, wrote a new function that overrides the old behavior, and the client's problem was solved.

I did all those without the original source code. It was a small thing to fix, but I can't imagine making the same fix so swiftly with any other tech stack, when the source code is lost. I was deeply impressed.

chikere232

That's pretty cool, but also, get version control

pizza

Version control won't help if the repo itself has been lost to the sands of time

chikere232

Very true.

Get backups

huijzer

Then don’t lose your repo xD

harperlee

It has been ages since I worked with Common Lisp, so I wonder: Would it be possible / make sense to have version control into the lisp image?

f1shy

Agree, but I think in this case would not help. It was not that a version was lost, but all. Had they had VC, probably the repo would have been deleted.

munchler

You don’t deliver source code with the binary at the end of the project?

coldtea

Many don't.

smokel

This is something I really like about Common Lisp, but after ~30 years of progress, I am a bit disappointed with improvements in the field of interactive development.

The process you describe is possible in JavaScript and many other languages as well. Updating a JAR is trivial, but doing it in a running system is uncomfortable.

The first thing to start with is versioning at the function, class, package, or module level. Combine that with versioning of data, and you've got my interest :)

pjmlp

JRebel, for those willing to pay.

lenkite

When will the next standard of Common Lisp be released ? 1994 is really showing is age.

nomilk

Looks like vim-slime is essential to how you work with CL + vim. I've only used vim for not even 2 years, and came across vim-slime 6 months ago when working in ruby and wanting to quickly 'send' code from editor (neovim) to rails console. 2 months ago I launched a startup and for hours every day had to swat/fix repercussions of bugs that weren't apparent pre-launch (as well as doing via the console things users needed but weren't able to do because the feature to do it hadn't been built yet). It was daunting. I don't know how I'd have managed without vim + vim-slime. Probably a lot of copy/pasting from vscode. Vim + vim-slime was at least a 2x productivity improvement, and >2x increase in developer happiness.

Another huge benefit of vim and vim-slime is it is immediately valuable when you use/learn any new language. So long as the language has a REPL/console/interpreter that can be opened from the terminal or terminal emulator in any form (e.g. CL, ruby, python, bash etc etc etc) then vim + vim-slime will be a brilliant ~IDE. (Possibly the only thing I haven't been able to do but wanted to is 'send' code from neovim to the javascript console in chrome, which would be pretty awesome!)

A side note: I found doom-emacs very similar to vim, only needed ~10 or so new keyboard shortcuts to be productive in emacs. (I still much prefer vim, but I'm not so down on emacs).

ofalkaed

>Looks like vim-slime is essential to how you work with CL

slime has some issues for me (obviously not OP) and I am not convinced lisp and vim are a good pair. lem is getting pretty good and improving by the day, find it much better to work with than vim when it comes to lisp and vim is my primary editor.

https://github.com/lem-project/lem

djtango

I have been using Clojure and before that Racket using only vim and vim-surround for almost a decade now.

I am sure I have left some productivity on the table not investing in workflows like cider etc but I have gotten a decent workflow using just vanilla tmux, a repl pane and vim-surround

The % matcher in vim does so much heavy lifting, I've never felt limited by a lack of slurp and barf

I actually wrote my own tiny plugin to send snippets to the repl using nc and I'm still happy enough tearing the clojure repl up and down and copying stuff in by hand because dealing with repl state can be pain. Even though I have at times had repls open for months, there is a freedom in just tearing it all down and up again.

Clojure itself has plenty of functions to load in files or snippets to help as well

quesera

> I don't know how I'd have managed without vim + vim-slime.

This is interesting -- I've worked with people who swear by common lisp, emacs, and SLIME.

I'm happiest with ruby and vim, but I have not tried vim-slime (nor even heard of it before, so thank you!).

But FWIW, my strategy for running larger bits of ad hoc code on the ruby/rails console is to:

  1. Add the code to a persistent local file (e.g. "ops_console_tools.rb")
  2. scp the file up to the target machine where I am running the irb/pry console
  3. In the console, run `load '/PATH/TO/ops_console_tools.rb'`
  4. Run the new code: `Ops::User::CustomReport.run(90.days.ago..)`
To keep things a bit more sane, all of the ad hoc ruby code is in modules, e.g. `Ops::User`.

And it helps to include some code to clear constant definitions which would otherwise complain to STDERR if you update and reload the file multiple times.

None of this is as awesome as SLIME of course, but it's pretty tolerable with a bit of readline-style up-arrow and command-history conveniences.

Disclaimer: Of course, running ad hoc code in prod is frowned upon. But we're extolling the virtues of CL in this thread, so I'll confess to breaking best practices in environments where it's permissible! Also this process gives you syntax highlighting while editing without requiring config on target host, and you can include the file in version control for greater formality.

pritambaral

I replaced a similar workflow with SLIME-style `eval-defun` by just running the remote Ruby console within emacs, using inf-ruby. Combined with robe, you get code intelligence for free, but without the heavy and error-prone static indexing of Solargraph or Ruby-LSP.

tempodox

Yep, I'm using the `slimv` plugin for vim and the `swank` server in a running `sbcl` instance in a second terminal tab. Since I'm on macOS, I could build a keyboard shortcut in vim that automates opening the 2nd terminal tab with the “Lisp machine + swank” when I say “connect” in vim. slimv/swank practically make vim an IDE for Lisp.

ferfumarma

How do you learn vim-slime? I have used vim before, so I have basic skills there, but I get lost and run out of time when I try to figure out how the slime model works and how to create a lisp project.

Is there a tutorial you followed or a video you found useful? What was your starting fund of knowledge?

nomilk

Great question - it's shocking easy to learn. Just three steps 1. Install (via vim-plug, lazy.nvim, or whatever vim plugin manager you're using), 2. configure it. Depending on your terminal the instructions are a little different, but it should only take a few moments due to the brilliant instructions found here: https://github.com/jpalardy/vim-slime/tree/main?tab=readme-o... I use kitty so I add two lines to kitty.conf and it's all ready to go. But it will depend on your terminal/terminal emulator. The instructions in the readme should have you covered.

Then 3. use it. This is shockingly easy, open two panes in your terminal with neovim on one side and REPL/interpreter on the other. For example I have neovim with my ruby file on the left pane and a rails console on the right (but on the right could be SBCL, python interpreter, or any other interpreter). In neovim, move the cursor to the line you want to run and press ctrl + c twice in quick succession. It will 'send' that line to the interpreter on the right pane and run that line!

Note: The first time you do this you may be asked which pane vim-slime should 'send' the code to, with the numbers displayed over the panes. For example in kitty I'm usually sending to pane 2, so I press: 2 enter enter. If it was pane 5, I'd press 5 enter enter etc.

If the line of code is immediately proceeded by another line(s) it will run that/those as well (for example, a multi-line Active Record query). It will do the same if there's one or more lines immediately above the current line. This takes a tiny bit of getting used to as you may unintentionally run lines immediately above/below the line for a short while.

That's all there is to it!

A few tips

- As explained above, ctrl + c ctrl + c will run the line under the cursor. But you can also select and run any code you want by selecting it with vim's visual mode and ctrl + c ctrl + c to run that selected code. For example, if you want to run part of a line, select it in visual mode and ctrl + c ctrl + c and it will run! Same for say a few hundred lines of code: select it all in visual mode (e.g. v ctrl + f ctrl + f then j or k to get to the exact line), then ctrl + c ctrl + c will run everything you selected.

- Rails specific: The rails console has a pager set to 'on' by default (this would necessitate back and forth between panes in order to press 'q' to quit out of the pager). So I turn it off by adding one line (IRB.conf[:USE_PAGER] = false) to ~/.vimrc or just .vimrc in the project directory.

Let me know if you have any questions/troubles.

jwr

That is a very interesting journey — mine was exactly opposite, after many years with Common Lisp, I moved to Clojure and wouldn't even think of going back. I find it intriguing that the author would want to move in the other direction, especially as concurrency was mentioned (one of the main reasons why I initially looked at Clojure).

I wonder what it was about babashka that didn't work for the author. When I need a quick script with batteries included, I use babashka and it works great.

djha-skin

I had already written large, nontrivial apps (linked in article) which required more libraries than babashka was written with, including ones I had written but also others. I therefore needed to run native-image on my own codebase, as it was not runnable from within babashka (at the time? I don't know if it is now).

Running native-image on an already established, not-written-for-it codebase is a nightmare. I just tried again some months ago on the linked code bases. native-image wouldn't budge. Kept getting hung up on I don't even know what, the errors were way too opaque or the app just misbehaved in weird ways.

jwr

Ok, that explains why babashka wasn't suitable. I still wonder, though, about the requirement to have an executable.

I still remember many years of reading comp.lang.lisp, where the #1 complaint of newcomers was that "common lisp cannot produce a native executable". I remember being somewhat amused by this, because apparently nobody expected the same thing from other languages, like, say, Python. But apparently things have changed over the years and now CL implementations can produce bundled executables while Clojure can't — how the tables have turned :-)

anta40

I think various Lisp implementations have their own way to do it, e.g save-lisp-and-die on SBCL.

But, if what you mean "executable" is "small compact executable like the one build by C/C++/Pascal, without extra Lisp runtime attached", perhaps you better look at something else, well like C.

lispm

There is already confusion. Things are different and the same words (executable, native, image, ...) mean slightly different things.

In the CL world it is usually relatively easy to create an executable. For example in SBCL I would just run SBCL and then save an image and say that it should be an executable. That's basically it. The resulting executable

* is already native compiled, since SBCL always compiles native -> all code is AOT compiled to machine code

* includes all code and data, thus there is nothing special to do, to change code for it -> the code runs without changes

* includes all the SBCL tools (compiler, repl, disassembler, code loader, ...) thus it can be used to develop with it -> the code can be still "dynamic" for further changes

* it starts fast

Thus I don't need a special VM or special tool to create an executable and/or AOT compiled code. It's built-in in SBCL.

The first drawback: the resulting executable is as large as the original SBCL was plus any additional code.

But for many use cases that's what we want: a fast starting Lisp, which includes everything precompiled.

Now it gets messy:

In the real world (TM) things might be more complicated:

* we want the executable to be smaller

* we want to get rid of debug information

* we need to include libraries written in other languages

* we want faster / more efficient execution at runtime

* we need to deliver the Lisp code&data as a shared library

* we need an executable with tuned garbage collector or without GC

* the delivery structure can be more complex (-> macOS application bundles for multiple architectures)

* we want to deliver for platforms which provide restrictions (-> iOS/Apple for example doesn't let us include a native code compiler in the executable, if we want to ship it via the Appstore)

* we want the code&data be delivered for an embedded application

That's in the CL world usually called delivery -> creating an delivered application that can be shipped to the customer (whoever that is).

This was (and is) typically where commercial CL implementations (nowadays Allegro CL and LispWorks) have extensive tooling for. A delivered LispWorks application may start at around 7MB size, depending on the platform. But there are also special capabilities of ECL (Embeddable Common Lisp). Additionally there were (and still are) specialized CL implementations, embedded in applications or which are used as a special purpose compiler. For example some of the PTC Creo CAD systems use their own CL implementation (based on a ancestor implementation of ECL), run several million lines of Lisp code and expose it to the user as an extension language.

iLemming

> I moved to Clojure and wouldn't even think of going back.

It's always amusing to watch programmers arguing for superiority of their favorite language(s) over others, often bashing language features without clearly understanding their purpose.

And it is especially amusing to watch Lispers trying to argue with each other. "I chose Scheme", "I picked Clojure", "I moved to CL"... etc.

Bruh, I move from one Lisp to another based on my current needs. It's harder for me to choose new shoes than switching between Lisp dialects. Once you learn any Lisp to a sufficient level, the mental overhead between Lisps becomes almost negligible - it feels like practically operating the same language.

Sure, each Lisp is unique and they all have different flavors, but seriously, shouldn't we celebrate the diversity and be happy that we have at least one Lisp for every platform?

@jwr This isn't meant as criticism of your comment. I'm not arguing at all with what you wrote; Clojure is my favorite Lisp flavor as well. I'm just taking a sentence out of context and using it as a cue for my rant - don't be mad, we're cool.

jwr

Yes, I think this is perpendicular to my comment. I stopped arguing about language superiority a long time ago, my comments were intended to reflect my own journey only.

Naru41

More than decade ago, I didn't understand an actual value of Lisp, but I remember this song well.

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

crispyambulance

Every programming language should have a music video!

iLemming

Heh, every function in every Lisp needs its own song (:

Here's one for clojure.core/lazy-cat

https://suno.com/song/d012fa48-d5c1-46f4-8561-9a031cfb8925

Makes me sad that Clojure has mapcat and lazy-cat functions, but they've never made any effort to create mapdog and lazy-dog variants. I firmly believe that is the unique and only factor that has prevented Clojure from becoming a mainstream language at the top of the RedMonk chart.

BreakMaker9000

Wondering whether a dialect like Jank [1] may be worth a shot?

[1] https://jank-lang.org/

cylinder714

Its author is quitting his job to work on it full time: https://jank-lang.org/blog/2025-01-10-i-quit-my-job/

aidenn0

As a counterpoint to author's use of vim-slime (not to say I don't believe author's commet of "I'm Okay, I Promise," but rather to communicate to others who are facing a similar choice:

I am a lifelong vim user (since elementary school in the early '90s), and I developed common lisp using vim for over a decade. I still use vim for nearly everything, but emacs as my Lisp IDE. Before evil-mode, I used the mouse and menus for 90% of what I did, and it was still an improvement over the best vim had to offer at the time (vim-slime existed back then, but would crash or hang regularly).

Author's vim setup is fairly good, but Emacs/slime is still better. They stopped using emacs because of RSI, but their vim setup defaults to "v a ( C-c C-c" to accomplish something that is "C-c C-c" in emacs/slime. They have altered it to be "v a ( <space> g" which begs the question of "why not remap keys in emacs?"

pntripathi9417

I have been working with Clojure for 5+ years now. For CLI applications babashka has worked quite well for us.

Would love to know more about the problems you faced.

In my experience whenever I faced such issues - it has been because I am not using it well.

For CLOS kind of things I have found https://github.com/camsaul/methodical library quite well and the performance is better than default multimethods in core clojure implementation.

chii

> spent long, hard hours banging my head against native-image and it just wasn't working out.

it would be nice to know what exactly isn't working out and what the problems with native-image was.

Coz i think clojure is as close to perfect, imho, as a language can go without selling out.

huahaiy

Graalvm native image for Clojure is a solved problem. Just add this library to the project and add a flag to native image command line.

https://github.com/clj-easy/graal-build-time

This initializes Clojure classes at build time and it mostly works for pure Clojure code.

Doing complicated things (e.g. depending on native library, etc.) requires some tweaking. For example, a few packages may need to be declared as initialized at build time or run time, depending what they are doing. And any unresolved classes need to be added to reflection-config.json.

All these are easily discoverable if one talks to people in the Clojurian slack channels. Clojure is a small community, so it helps to be part of it, because there are not a lot of open materials on the Web.

IshKebab

> solved problem

except...

> mostly works

> requires some tweaking

> discoverable if...

I know nothing about Clojure but from your caveats I think I can see why he spent hours banging his head against a wall.

chii

when engineers say it's a solved problem, they mean it in the same way as a mathematician saying a theorem is trivially proved.

macmac

Look at the hoops OP had to jump through to get SBCL working on Windows. I think Graal would compared favourably with that.

djha-skin

I actually just dusted off my old Clojure stuff to see if it was a "solved problem", and it isn't.

I grant that it might be described thus if I started out with that stack, but trying to retrofit an older code base with it is, I have found, next to impossible. You have to code around the myriad gotchas as you go or you're never going to identify all those landmines going back over it after the fact. The errors and bad behaviors are too difficult to identify, even for the `native-image` tooling.

huahaiy

No. I have looked at your code. You did not use the mentioned https://github.com/clj-easy/graal-build-time

If you don't do what everybody is doing to solve a problem, then of course it is not a "solved problem" for you.

No, you don't need to code specifically for native-image. What are the landmines that you need to code around? Since you have not successfully compiled native-image by following common practices, you obviously don't know.

m1n1

what are you referring to when you say a language can sell out?

thih9

Related, Janet: https://janet-lang.org/

I especially like its github readme and the FAQ there, provides a good amount of context about the project: https://github.com/janet-lang/janet

draven

It's mentioned in the article:

> If I had heard about Janet when starting this hunt, I might have stopped there and not gone on to CL. Nice syntax, small, fast executables, C FFI, a fun intro book. It checks all my boxes.

rcarmo

It's great, but I found the library ecosystem lacking for my particular use cases . The joy web framework, in particular, seems to have stalled in time.

bigpeopleareold

I am looking at CL myself, but my needs are more hobby than anything, but I want to convince myself I can find it useful for my own work in certain things (e.g. I want to maybe use Bike to run C# code in an SBCL REPL.)

The feedback loop one gets with it is insanely fast, even faster than Python (certainly there are exceptions even with Python ...) That's a blessing and curse for me - the tighter a feedback loop, the harder for me to get out of a problem I am stuck on. :) But, so far, I felt like I can write a thing and not worry about running it ... type a thing, quickly get it running it in a running REPL loop. If a mistake happens, I can fix the issue right there, instead of just a long stack trace. For what it is worth though, I have been doing this in Emacs for a long time (well, for small functions), but didn't think much of it until now.

WorldMaker

If you are wanting to play with C# in a REPL (although C# has a REPL these days; it's not great but it works), F# could be worth a try. I know the Scheme/ML family split means not everyone who likes CL/Scheme likes the ML family (and vice versa), but F# is a "good one" for what that is worth.

darthrupert

Ah, it's this time of the year when we get to fantasize about cool platforms and languages before succumbing back to python, typescript or feeding the relentless AI monster in a major cloud provider.

worthless-trash

Because we are not allowed to program in another language that 'most' programmers can't understand immediately.

We are cursed to use the lowest common denominator of choices of programming.

andsoitis

> Because we are not allowed to program in another language that 'most' programmers can't understand immediately

If everyone in a code base wrote in a different programming language, the code base would suck.

worthless-trash

I have heard that LLM's transpile between languages perfectly. They'll be able to fix problems like this.

However on a serious note, "the industry" fears even writing the entire codebase in a 'non common' language because of it, let alone your strawman idea of multiple languages.

baq

Typescript is great! Except the JavaScript below it which is terrible, but the static analysis and constraints I can express in typescript are amazing.

It’s actually surprising that there’s no typing discussion in this thread.

stevebmark

I don't understand the "Requirements Met" section, that reasoning applies to almost any programming language. You chose Common Lisp because there's a JSON library?

neuroelectron

A lot of these intermediary languages are not trivial to parse safely and are a vector for exploits. It's not something you can really do on your own unless you're just supporting a specific subset for your application. Even then, you really need to know what you're doing.

forgotpwd16

There's a section "hunt for new Lisp". It isn't explicitly stated in the requirements maybe because it can be inferred from there that being a Lisp is also one.

IshKebab

Yeah I thought he would go with Rust or Go after seeing those requirements.

Clearly there was another implicit requirement - maybe it had to be a niche language?

weikju

Probably had to be a Lisp, considering the OP was coming from Clojure. Rust and Go fail that (unwritten) requirement.

null

[deleted]