Common Lisp Is Not a Single Language, It Is Lots
By Artyom BologovBeing a part of the Lisp community, I often see a talk of "Lisp" the language. Like here, for example. What is meant by "Lisp" is often uncertain. But Common Lisp seems to be the most worthy contender for the title. It's "Common", after all. One true Lisp to rule them all.
But Lisp is a family of languages. "Lisp" might mean anything else—Scheme(s), Clojure, original LISP, and any of its descendants. So there's no "The Lisp", and implying it is misleading.
But there is "The Common Lisp", right? It's a standard well-defined language with a name to it. It's one coherent thing, one can say.
It's not. In this post, I'll try to show how many languages there are in CL. Hopefully helping one to understand why saying "Common Lisp is bad" or "Common Lisp is beautiful" is always playing with the (mis)conception of CL others have.
I'll list the example code in each of these. The example snippet will be a simplistic implementation of Gemtext parser. Don't expect super correct and beautiful code. I'm doing it for the sake of example, after all.
The Language?
Probably the biggest thing that "proves" Common Lisp integrity is the book. "Common Lisp The Language". Funnily enough, Wikipedia says
But let's see whether there is The Language in the book.
The book is 1000 pages long.
And I'm not able to focus on anything longer than 30 pages.
So I'll take a different direction: that of a programmer exploring the API.
The API of trivial-cltl2
, the CLtL portability library.
Here's a listing of symbols that
trivial-cltl2
exports in addition to standard ones:
And that's it. Not much. CLtL2 is the same as ANSI CL.
But notice what the list is lacking: the notion of objects. So CLtL2 (as both the book and the language), although including CLOS and other niceties discussed below... Is not object-oriented at its initial core. And all the mentions of CLOS etc. are only happening near the end of the book. An epilogue. Appendix. Erratum.
So we have our first language (out of many): CLtL. A language built out of variables, functions, and maybe structures. Like Scheme, but batteries included. Small and neat nonetheless. Here's how our parser could look in The Language:
No fancy DSLs, no Turing-complete APIs, no OOP. Speaking of which, another Common Lisp language:
Common Lisp Object System
OOP is but a one approach to building Lisp software. There are CL codebases built entirely out of plain functions. Even the ones built entirely in a Scheme-y functional style (I worked with some.) There are others built out of objects and methods on them. There are ones that are built around And it's quite hard to work with either if one's typical style is not the same as that of the codebase.
So here goes our first non-CLtL language: CLOS. Defining classes and methods on them:
The code is more verbose than that in The Language.
Yet it's also more reliable and introspectable.
All these classes add type checking and convenient slot access with e.g. with-slots
.
The behavior is more overridable with :around
methods.
And the code can be extended with more classes.
I must admit that I cheated by reusing the code from above. So the parser stays kind of the same procedural piece of code. It's enhanced with more types and MOP introspection. But it still is procedural, because OOP (not the CLOS, but the general idea) is merely a facade over procedural programming, encapsulating the procedural behavior into classes. One can implement it with methods and character dispatch. But that's no longer a CLOS-y code, it's...
Generics And Protocols
Generics are part of CLOS, right? Why make them a separate "language" then? My reasoning is: one can program anything in them without touching classes. And if you can, then it's a separate language alright.
Generics shine the most when there's a library intended for extension. Like lots of libraries by Shinmera or Robert Strandh, where there's
- A general protocol.
- And implementation-specific extensions to it with the actual behavior.
Here's a weird version of the Gemtext parser with generics:
That's where the example stops being useful. Generic style is nice, but this piece of code is by no means representative. I must've used classes for elements. I must've made a simpler protocol. I must'ven't used lists for state. I might have used state machine to drive the parsing.
But even this ugly code has the benefits of generics:
- It is easy to extend: just dispatch over the new character.
- It provides a strict protocol with type checking—if something goes wrong, a
no-applicable-method
signals and shows the problem. - It makes the behaviors compose nicely and puts recursion termination into the method of its own.
Try generics for your new projects. They are nicer than I represented them here. Speaking of "nicer", how about some syntactic sugar?
Loop Macro
Using loop
is always a risk: some Lispers love it, and some hate it.
But one thing is undeniable: it's powerful and reads like English.
Well, given that it's used right.
And no, it's not just "another iteration construct".
loop
has:
- Symbol binding.
- Conditionals.
- Iteration over (almost) all the standard data types.
- Data aggregation.
So loop
is quite a free-standing entity.
I've proof:
I completed two weeks of Advent of Code in it.
So let's try implementing this simple Gemtext parser as one huge loop:
Beautiful, isn't it? Ugly, isn't it? Love it or hate it, it's a nice readable syntax. And you can advertise this small language to your C-family friends. "This is The Lisp, try it!" I did this once, and it kinda worked...
Clojure?!
The legend goes: Rich Hickey made Clojure after being irritated by ABCL. So Clojure feels (after programming some sufficiently big projects in it)... Lispy (sorry for the linguistic abuse here). Like an opinionated standard lib over CL. Some reader macros here and there. Square and curly braces. Some nicer functional programming constructs. More consistency and less burden of the past. A fancy OOP system based on interfaces.
We could've had that in CL. And there are projects enabling a "nicer CL", or even outright implementing Clojure in CL! Many CL programmers squint at them. These extensions are going "too far" making a different language out of CL. Yes, yes, yet another Common Lisp language.
Notable Mention: Format
format
function is an enormous text formatting DSL with
- Conditionals.
- Iteration.
- Pretty-printing.
- Recursive function calls.
- And something I surely forgot to mention.
Here is how one of my format
strings looks like:
Horrible, but it gets the job done. And it's much more effective than writing 100+ lines of printing code.
So while I'm hesitant about calling
format
another Common Lisp language...
It is alien enough to be one.
See also: a case against format!
Conclusion
There's no Common Lisp The Language. There's a set of languages sloppily stitched together. Well, less sloppy than C++ anyway. Agree or disagree, these are quite orthogonal to each other:
- Core Scheme-y Common Lisp.
- CLOS.
- Generics.
-
loop
macro - Clojure and Scheme?!
-
format
strings.
A lot of sub-languages for one (sorry) "language", right?
(Drafts of this post also included sections on namespaces and OS interfaces. I decided to drop them to save space and because they weren't adding much. Lots of languages!)