Antoine Kalmbach's blog

Between two Lisps

Out of all Lisps the ones I’ve come to appreciate the most are Scheme and Common Lisp. These two languages are fundamentally very different: Scheme is a minimalist language built on the foundations of lambda calculus, while Common Lisp is a multi-paradigm synthesis of many Lisps before it. Common Lisp is a large standard with many implementations, Scheme is a collection of an evolving but minimalistic standard with many implementations. The core of Scheme is quite small compared to Common Lisp. The latest standard R6RS is about 65 pages, while the ANSI Common Lisp standard from 1994 is about 1100 pages. The Scheme Requests for Implementation process aims to standardize additional features (like test suites) that implementations may implement.

Common Lisp has some wonderful features, conditions and restarts, the Common Lisp Object System (CLOS), the macro system, among many other things. The SLY IDE for Emacs is amazing, and the Steel Bank Common Lisp compiler is really great. It has Quicklisp and ASDF for package and build management, respectively. I find the developer experience of Common Lisp to be superior to almost anything imaginable, and this is not an empty statement: having used all sorts of IDEs and editors for over 25 years, I have seen many.

Tastes differ

That said, Common Lisp is weird. What I find particularly jarring is that functions and variables live in different namespaces: if you put a function into a variable, you can’t just use it like a function, you have to funcall it. Having programmed in lots of languages of the ML family this is just, well, odd; but this is due to historical reasons and there are sound technical reasons for it. There are other oddities, some strange things like (cdr '()) is not an error (in Scheme it is), () and nil are equal (in Scheme #f and () are separate things), and so on.

This isn’t really a fault in Common Lisp: other languages have impacted my taste and preferences to bias me in the direction of Scheme, but that is not to say I cannot work with Common Lisp’s idiosyncracies. Actually, I don’t mind them, I just notice them.

I like the naming styles of Scheme more, as well. It has string? vs string-p for predicate functions, set! for state modifying functions, foo->bar for conversions, these make code quite easier to read. Scheme has hygienic macros, Guile has the traditional defmacro as well.

Many nice Scheme features are available in Common Lisp libraries. Pattern match is available in the trivia library. Named lets are easy to implement with a macro.

Common Lisp aficionados are quick to point out things Scheme doesn’t have: keyword arguments, docstrings, rest arguments, but my Scheme implementation of choice Guile has these built into the language.

Productivity matters

Guile is in a strange niche is that its primary raison d’être is to be an extension language for the GNU project. Like Emacs Lisp is for extending Emacs, Guile is the de facto language for GNU programs for extension and scripting.

Guile doesn’t have Quicklisp and its package manager and build system is basically nonexistent for the first and Autoconf for the second. There is sentiment in the Guile community to have Guix as the package manager for Guile. This might sound a bit onerous, since Guix is also a complete package management for many other things than Guile, but consider this: as Andy Wingo points out in his message that Guile libraries often come with C extensions, Guile packages need some sort of managed build system for building the C extensions. Since it doesn’t have one, to solve the problem of building a package manager you’d also have to build a build system that can manage C code and packages needed by the C code bits. To do this elegantly is a gargantuan task, for instance, chicken-install just asks you to put compiler/linker flags on the command line before calling it, so it obviously is a hard problem.

Now, Guix solves all that, and more, in a manner that is quite elegant and interesting. But it’s not as lightweight as something like Quicklisp or chicken-install from CHICKEN Scheme. What is more, Guix works only on GNU/Linux systems really, so macOS and Windows users won’t be able to do use your library if you plan on distributing it via Guix. Duh, it’s the GNU project, but portability is always nice.

But in Common Lisp I can write (ql:quickload :alexandria) and voilà, it will automatically install the Alexandria library that I can use. Then again, in Common Lisp it’s somewhat rarer to have C extensions, so I don’t know how ASDF handles that.

It is unfair to compare SLY to Geiser, the best Emacs Scheme integration package. SLY is based on SLIME which has decades of man-years of work behind it. Geiser is able to support multiple Scheme implementations and it is quite impressive in this regard. But SLY obviously has much more (e.g. an interactive debugger). That said, Geiser has nice Guile support, and Guile is in general the most Common Lisp-y of all Schemes, in fact, it has

  • an imitation of CLOS in the form of GOOPS
  • docstrings and keyword, rest, and optional arguments for function definitions
  • an interactive REPL and a mutable top-level (unlike many Schemes)
  • a nice module system
  • defmacro and exceptions somewhat similar to Common Lisp restarts

and some nice things Common Lisp doesn’t have like first-class continuations. But then again I would use those rarely.

When would I pick one over the other?

If I were to write an extensible C/C++/Rust program that I know will need to use a low-level language, I might do the low level bits using a low level language and then write the rest in Guile. Guile makes it very easy to spin up a REPL socket to do something like myprogram --repl=12345 that you can connect to, and the interop between C and Guile is fantastic, it has to be, as it’s primarily an extension language.

On the other hand, if were to build a wholly standalone application, the choice is not as obvious. I could do the whole thing in either language. Common Lisp can build native executables, although their size will be large (who cares?) since the binary will include the whole Lisp implementation. Guile cannot compile to native code so you’ll have to write a script executable, but that doesn’t really matter.

Scenarios where I would pick Guile:

  • when I’m making an extensible program that has to have some C/C++ bits but I want to make it scriptable by users
  • when I want to write a native binary but not write too much C/C++ code
  • I just want to write Scheme, because Scheme is a bit more elegant
  • the project has something to do with the GNU project

On the other hand, Common Lisp makes the most sense if I just want to enjoy a seriously rapid development experience, a large standard library and language, and I don’t mind having 50MB binaries (again, who cares?) if I were to write standalone programs. Guile can’t do that, but it’s easy to either write Guile scripts or a C program that essentially bootstraps a binary to load a Scheme runtime. This is how LilyPond is written, for example.

So the answer to which one is decidedly both! Both languages are really fun to write. At the moment I don’t do Lisp at my day job so it’s all for fun anyway. If this were for professional purposes, I don’t know. Very often a strict requirement for professional work is to be able to be productive. In that regard I think Common Lisp has a significant edge, not only due to its superior development experience, but its history as a real production language. There are actual companies doing stuff in Common Lisp. I have not heard of any professional (in the industrial sense) uses of Guile, notwithstanding that many projects powered by Guile (Guix, etc.) are extremely professional in the way they are written and maintained. But Common Lisp has more libraries and companies behind it.

What about Clojure?

I’ve actually had the pleasure to use Clojure for personal fun, and a little bit of professional use. I wrote a couple of libraries (vigil and task) and my experience with has always been positive and its development experience is excellent. Clojure isn’t a true Lisp, or well, it is part of the Lisp family. Its ability to interface with the JVM makes it easy to leverage the thousands of JVM libraries out there.

I’d gladly do it again, though I feel that Common Lisp with CLOS and its module system makes it somewhat easier to practice programming in the large. Clojure explores interesting territories with spec, so it is interesting to see what direction the language will take in the future.

Final words: Emacs Lisp

There’s also a parenthetical elephant in the room here: Emacs Lisp! An old descendant of MacLisp, it’s actually quite fun to write, and the fact that Emacs itself is the interpreter, you have superb introspectability for any Elisp code. Most of the Lisp I write these days is probably Emacs Lisp. It’s very close to Common Lisp. cl-lib adds a sufficient amount of convenience from Common Lisp to make Elisp writing quite enjoyable. EIEIO adds a subset of CLOS that can be used seamlessly with cl-lib. The condition system is missing.

I’ve also done a fair bit of Fennel, a Lisp that compiles to Lua. I used it to write a small game and it was fun to write since you could develop the game in a REPL.

All in all, Lisp in all its variants is the most fun I’ve ever had while programming a computer. Guile and Common Lisp are definitely the most fun I’ve had programming in Lisp.

Previous: How I built this website Next: No more microblog