+++
title = "A Road to Common Lisp"
snip = "How I learned Lisp, and you can too."
date = 2018-07-03T16:00:00Z
draft = false
+++
I've gotten a bunch of emails asking for advice on how to learn Common Lisp in
the present day. I decided to write down all the advice I've been giving
through email and social media posts in the hopes that someone might find it
useful.
<div id="toc"></div>
## Context
I think it's important to have a general sense of where Common Lisp came from
and what kind of a language it is before you start learning it. There are a lot
of things that will seem very strange if you're coming straight from modern
languages, but will make a lot more sense if you've got a bit of background
context.
### History
Lisp and Common Lisp have a long, deep history. I'm not going to try to cover
it all here — if you're interested you should check out some of the following
(in roughly increasing order of detail):
* Wikipedia's [History of Lisp][wiki-history-lisp] and [History of Common Lisp][wiki-history-cl].
* The [Where it Began section in Practical Common Lisp][pcl-history].
* The [History: Where did Lisp come from?][cll-history] section of the comp.lang.lisp FAQ.
* [Common Lisp: the Untold Story][untold] by Kent Pitman.
* [The Evolution of Lisp][evolution] by Guy Steele and Richard Gabriel.
I realize that you probably won't want to read all of the links above
immediately, so here's a whirlwind tour of sixty years of Lisp.
Lisp began in the late 1950's. It was invented by John McCarthy at MIT.
Over the next twenty or so years various versions and dialects of Lisp grew and
flourished. Some of the more notable dialects were Maclisp, BBN Lisp/Interlisp,
Franz Lisp, Spice Lisp, and Lisp Machine Lisp. There were others too. The
point is that there were a *lot* of different implementations, all growing,
changing, and trying out different things.
(Scheme also originated in this time frame, but took a very different route and
diverged from the path we're looking at. I won't cover Scheme in this post.)
In the early 1980s people decided that having a whole slew of
mutually-incompatible dialects of Lisp might be not be the most desirable
situation to be in. An effort was made to take these different languages that
had grown organically and produce one common language that would satisfy the
needs of everyone. In 1984 the first edition of Guy Steele's [Common Lisp: the
Language][cltl] was published.
If you do some math you'll see that at the time the book was published Lisp had
around twenty-five years of real-world use, experimentation, experience, and
history to draw upon. Even so, the book alone didn't quite satisfy everyone and
in 1986 a committee (X3J13) was formed to produce an ANSI specification for
Common Lisp.
As the committee worked on the standardization process, in 1990 the second
edition of Common Lisp: the Language was published. This was more comprehensive
and contained some of the things the committee was working on (see the
comp.lang.lisp FAQ linked above for more on this). At this point the Lisp
family of languages had over 30 years of experience and history to draw upon.
For comparison: Python (a "modern" language many people think of as also being
"kind of old") [was released][python] for the first time the following year.
In 1992 the X3J13 published the first draft of the new ANSI standard for Common
Lisp for public review (see Pitman's paper). The draft was approved in 1994 and
the approved specification was finally published in 1995. At this point Lisp is
over thirty-five years old. The first version of Ruby [was released][rubby] in
December of that year.
That's the end of the history lesson. There has not been another revision of
the ANSI specification of Common Lisp. The version published in 1995 is the one
that is still used today — if you see something calling itself "an
implementation of Common Lisp" today, that is the specification it's referring
to.
### Consequences
So why am I telling you all this? Because I want you to know what you're
getting yourself into. I want you to realize that Common Lisp is a stable,
large, practical, extensible, ugly language. Understanding these
characteristics will make a lot of things make more sense as you learn the
language.
#### Escaping the Hamster Wheel of Backwards Incompatibility
If you're coming from other languages, you're probably used to things breaking.
If you want to run Ruby code written ten years ago on the latest version of
Ruby, it's probably going to take some time to update it. My current day job is
in Scala, and if a library's last activity is more than 2 or 3 years old on
Github I just assume it won't work without a significant amount of screwing
around on my part. The hamster wheel of backwards incompatibility we deal with
every day is a fact of life in most modern languages (though some are certainly
better than others).
If you learn Common Lisp, this is not the case. In the next section of this
post I'll be recommending a book written in 1990. You can run its code,
unchanged, in a Common Lisp implementation released last month. After years of
jogging on the hamster wheel of backwards incompatibility I cannot tell you how
much of a relief it is to be able to write code and reasonably expect it to
still work in twenty years.
Of course, this is only the case for the language itself — if you depend on any
libraries there's always the chance they might break when you update them. But
I've found the stability of the core language is contagious, and the Common Lisp
community seems overall fairly good about maintaining backwards compatibility.
I'll be honest though: there are exceptions. As you learn the language and
start using libraries you'll start noticing some library authors who don't
bother to document and/or preserve stable APIs for their libraries, and if
staying off the hamster wheel is important to you you'll learn to avoid relying
on code written by those people as much as possible.
This may sound a little bleak, but it's made a bit better by some other
characteristics of Common Lisp: it's a large, practical language. The second
edition of Common Lisp: the Language (usually abbreviated as "CLtL2" by Common
Lisp programmers) is 971 pages long, not including the preface, references, or
index. When programming in Common Lisp people will tend to depend on
a small(ish) number of libraries, and library writers often try to minimize
dependencies by utilizing as much of the (large) core language as possible.
I personally try to stick to fewer than ten or so dependencies for my
applications and no more than two or three for my libraries, but I'm probably
a bit more conservative here than most folks (I *really* don't like the hamster
wheel any more).
It's also worth noting that since Common Lisp has been around and stable for so
long, it has *libraries* older and more stable than many programming languages.
For example: Bordeaux Threads (the de-facto threading library for Common Lisp)
was first proposed in 2004 and released soon after (2006 at the latest but
possibly earlier, it's hard to tell because so many links are dead now), which
makes it fourteen years old. So yes, threading is handled by a library, but I'm
not worried about it breaking my code in the next decade or two.
As you learn Common Lisp and look for libraries, try to suppress the voice in
the back of your head that says "This project was last updated six years ago?
That's probably abandoned and broken." The stability of Common Lisp means that
sometimes libraries can just be *done*, not *abandoned*, so don't dismiss them
out of hand.
#### Power
Part of Common Lisp's practicality comes from its extensibility. No one has
been clamoring for a new version of the specification that adds features because
Common Lisp gives users enough power to add new features to the language as
libraries without having to alter the core language.
Macros are what might come to mind when you hear "Lisp extensibility", and of
course that's part of it. Macros allow users to write libraries that would need
to be core language features in other languages.
Common Lisp doesn't include string interpolation. You want it? No problem, you
don't have to wait for [Scala
2.10](https://docs.scala-lang.org/overviews/core/string-interpolation.html) or
[Python 3.6](https://www.python.org/dev/peps/pep-0498/), just [use
a library][cl-interpol].
Want to try some nondeterministic programming without any boilerplate? [Grab
a library][screamer].
Pattern matching syntax can make for some really beautiful, readable code.
Common Lisp doesn't include it, but of course [there's a library][trivia].
Enjoying algebraic data types in Haskell or Scala? Here's your [library][cl-adt].
All of these libraries rely on macros to make using them feel seamless. Of
course you could *do* all of that without macros, but you've have to add a layer
of boilerplate to manage evaluation. This:
(match foo
'(list x y z) (lambda (x y z) (+ x y z))
'(vector x y) (lambda (x y) (- x y)))
just doesn't flow off the fingers like:
(match foo
((list x y z) (+ x y z))
((vector x y) (- x y)))
No one's up in arms trying to get a new revision of the Common Lisp standard to
add pattern matching because you can write it as a library and get 95% or more
of what you've get if it were built in. The language gives you enough power to
extend it in a way that feels like the extension was there from the beginning.
Macros are one of the things that make Lisp so incredibly extensible, because
they let you transform arbitrary code into other arbitrary code. This is true
for macros in languages like C too, but Common Lisp macros are fundamentally
different because they're *part of the language*.
In C you have a layer of macros on top, written in a separate preprocessor macro
language. The macro layer and the language layer are separate from each other,
with the macro layer providing one one extra level of abstractive power.
In Common Lisp, you write macros *in Common Lisp itself*. You can then use
those macros to write functions, and use those functions to write more macros.
Instead of two stratified layers it's a *feedback loop* of abstractive power.
But macros aren't the only thing about Common Lisp that make it so practical and
extensible. Something that people often don't realize is that while Common Lisp
is an extremely high-level language thanks to macros, it also has plenty of
low-level facilities as part of the language. It's never going to be as
low-level as something like C or Rust, but you might be surprised at some of the
things that the ANSI spec includes.
Want to see the assembly that a particular function compiles down to?
[`DISASSEMBLE`][dis] it!
Want to stack-allocate something to avoid some garbage collection? X3J13
[thought of that][dynext].
Need arrays of unboxed floats to ship to a graphics card? [The standard allows
for that][arrays].
Think `GOTO` should be considered helpful, not harmful? Well okay, we're all
adults here. [Good luck][tagbody], try not to shoot your foot off.
Need to do unsigned 8-bit arithmetic in your Game Boy emulator, but would prefer
it to compile down to just a machine instruction or two? [It's
possible][arith].
Not all Common Lisp implementations actually perform all these optimizations,
but the designers of Common Lisp had the foresight to include the language
features needed to support them. You can write vanilla Common Lisp as defined
by the standard and trust that it will run everywhere, and implementations that
*do* support these kinds of things will take advantage of the optimization
opportunities.
This combination of supporting extremely high-level programming with macros and
a reasonable amount of low-level optimization mean that even though the
specification is over twenty years old, it's still a good solid base to build on
today. The thirty years of experience and history the designers were drawing
from allowed them to create a very practical language that has survived for
decades.
#### Ugliness
It's also important to realize that while Common Lisp might be incredibly
practical, the need to accommodate existing users and dialects means that there
are ugly parts. If you buy a paper copy of the second edition of Common Lisp:
the Language and look up "kludges" in the index you'll find this:
[![Photo of a page of CLtL2's Index, listing "kludges" as pages 1 to 971](/media/images/blog/2018/07/lisp-kludge.jpeg)](/media/images/blog/2018/07/lisp-kludge.jpeg)
Common Lisp is not a beautiful crystal of programming language design. It's
a scruffy workshop with a big pegboard wall of tools, a thin layer of sawdust on
the floor, a filing cabinet in the office with a couple of drawers that open
perpendicular to the rest, and there's a weird looking saw with `RPLACD` written
on the side sitting off in a corner where no one's touched it for twenty years.
This historical baggage is a price paid to ensure Common Lisp had a future. It
made it practical for people using the old dialects to actually adopt Common
Lisp with a reasonable amount of effort. If the designers had tried to make it
perfect and beautiful the language probably would have been ignored as too
different to port implementations and code to, instead of being adopted and
embraced.
[wiki-history-cl]: https://en.wikipedia.org/wiki/Common_Lisp#History
[wiki-history-lisp]: https://en.wikipedia.org/wiki/Lisp_(programming_language)#History
[cll-history]: https://www.cs.cmu.edu/Groups//AI/lang/lisp/faq/lisp_2.faq
[pcl-history]: http://www.gigamonkeys.com/book/introduction-why-lisp.html#where-it-began
[untold]: http://www.nhplace.com/kent/Papers/cl-untold-story.html
[evolution]: https://www.dreamsongs.com/Files/HOPL2-Uncut.pdf
[cltl]: https://www.cs.cmu.edu/Groups/AI/html/cltl/cltl2.html
[python]: https://en.wikipedia.org/wiki/History_of_Python#Early_history
[rubby]: https://en.wikipedia.org/wiki/Ruby_(programming_language)#First_publication
[bt-release]: https://web.archive.org/web/20040831185622/http://ww.telent.net/diary/2004/7/#9.57181
[cl-interpol]: https://edicl.github.io/cl-interpol/
[screamer]: https://nikodemus.github.io/screamer/
[trivia]: https://github.com/guicho271828/trivia/wiki/What-is-pattern-matching%3F-Benefits%3F
[cl-adt]: https://github.com/tarballs-are-good/cl-algebraic-data-type
[dis]: http://clhs.lisp.se/Body/f_disass.htm
[dynext]: http://clhs.lisp.se/Body/d_dynami.htm
[arrays]: http://clhs.lisp.se/Body/15_ab.htm
[arith]: https://pdfs.semanticscholar.org/7089/0eff0e93aba49174a9346731f4bf9225706d.pdf
[tagbody]: http://clhs.lisp.se/Body/s_tagbod.htm
## A Road to Learning Common Lisp
If all of this hasn't scared you away from the language, let's talk about how
you can learn it in 2018.
If you search around on the internet for Common Lisp tutorials and guides,
you're not going to find as much as you might expect. This is because a lot of
Common Lisp reference material was created before and/or during the infancy of
the internet. There are a *lot* of books about Common Lisp out there. Some are
better than others. I'll recommend the ones I personally think are the best,
but don't hesitate to browse around and find others.
One final disclaimer: this is *a* road to Common Lisp, not *the* road to Common
Lisp. It's what I followed (without some of the dead ends) and has a lot of my
personal opinions baked in, but is by no means the only way to learn the
language. Ask around and get some more opinions — more options won't hurt!
### Get a Lisp
To get started with Common Lisp you'll need to install a Common Lisp
implementation. Remember: Common Lisp is an ANSI specification, so there are
multiple implementations of it, which gives you choices. There are a bunch of
options, but I'll make it simple for you:
* If you're comfortable with the command line, installing packages with a package manager, and already have a text editor you like, choose [SBCL][].
* Otherwise, choose [ClozureCL][CCL] (often called "CCL").
That's Clozure with a Z. Clojure is something entirely different that just
happens to have a confusingly similar name.
You might also hear of something called CLISP, which sounds like it might be
what you want. It's not. CLISP is just another implementation, but it hasn't
had a release in eight years (even though development is still ongoing in its
source repos) and it's not as commonly used as CCL or SBCL, so it'll be harder
to find help if you have questions about the installation, etc.
You might also hear about something called Roswell. Don't use Roswell, you don't
need it (yet (or at all)).
Just install SBCL or CCL for now, you can explore the other options once you've
got your bearings a bit better.
### Pick an Editor
You might hear people tell you that you *must* learn Emacs before learning
Common Lisp. They're wrong. You can get started learning the language just
fine in whatever text editor you're comfortable in.
If you don't have a preference, CCL itself comes bundled with a text editor on
MacOS. That one will work just fine to start.
Emacs, Vim, Sublime Text, Atom, whatever, for now it doesn't matter. As long as
it can balance parenthesis and highlight comments and strings, that's all you
need to start. Worry about shaving the editor yak once you're more comfortable
in the language.
### Hello, Lisp
To check that you've got everything set up properly, make a `hello.lisp` file
with the following contents:
:::lisp
(defun hello ()
(write-line "What is your name?")
(let ((name (read-line)))
(format t "Hello, ~A.~%" name)))
Don't worry about what this means yet, it's just a check that everything's
working properly.
Open an SBCL or CCL REPL and load the file by entering `(load "hello.lisp")`,
then call the function and make sure it works. It should look something like
this if you picked SBCL:
$ sbcl
* (load "hello.lisp")
T
* (hello)
What is your name?
Steve
Hello, Steve.
NIL
*
Or this if you chose CCL (the program might be annoyingly named `ccl64` if
you're on a 64-bit system):
$ ccl64
Clozure Common Lisp Version ...
? (load "hello.lisp")
#P"/home/sjl/Desktop/hello.lisp"
? (hello)
What is your name?
Steve
Hello, Steve.
NIL
?
If your arrow keys and backspace don't work in the REPL, use [`rlwrap`][rlwrap]
to fix that. `rlwrap sbcl` will give you a non-miserable REPL. `rlwrap` is
a handy tool to have in your toolbox anyway.
[rlwrap]: https://github.com/hanslub42/rlwrap
### A Gentle Introduction
The best book I've found for getting started in Common Lisp is [Common Lisp:
A Gentle Introduction to Symbolic Computation][book-gentle]. This book really
does strive to be gentle. Even if you've programmed before I'd recommend
starting here because it eases you into the language. If you find it's moving
too slow just skim forward a bit.
The 1990 edition is available free from the site, and there's a 2013 reprint
which fixes some minor errors in the 1990 version. If you can afford it I'd
recommend buying the 2013 edition, but the 1990 version will also do fine.
Go through the book and *do all the exercises*. This will take a while.
This is mainly meant to get you started overcoming some of the main obstacles to
being comfortable in Common Lisp, like:
* How am I ever going to remember all these weird function names?
* Why do people use strings so rarely?
* When do I need/not need the goddamned quotation mark?
You should also join the `#clnoobs` channel on Freenode so you can ask questions
if you get stuck. For the most part people there are friendly and helpful,
though I'll warn you in advance that there's at least one person who can
sometimes be abrasive.
If IRC isn't your thing there's also a [Discord
server](https://discord.gg/tffeu2x) that some of us hang out in. Join the
`#common-lisp` channel there and we'll be happy to help you.
[book-gentle]: https://www.cs.cmu.edu/~dst/LispBook/
[SBCL]: http://www.sbcl.org/on
[CCL]: https://ccl.clozure.com/
### Getting Practical
Once you've finished that book the next one you should attack is [Practical
Common Lisp][book-pcl]. You can get a paper copy if you want, but the full book
is available on the site.
You can skip the editor/programming environment part because the environment it
recommends (Lisp in a Box) is abandoned and no longer works. Just keep using
the programming environment you're comfortable with for now.
Unfortunately the book doesn't include exercises. If you *really* want to get
the most out of it you can type in all the code as you're reading it and poke at
it, but if you've already done the exercises in the previous book it's probably
safe to just sit down and read the book carefully.
Make sure you understand everything as you go through the book. Don't be afraid
to ask questions on IRC or Discord (or email me if you want, I don't mind) if
something's not clear.
You should also begin to get comfortable looking up things in [the Common Lisp
language specification][clhs] itself. It's the ultimate manual for Common Lisp.
It can be pretty dense at points, but can answer many questions you might have.
You can either use the index page to find what you're looking for or just search
on Google for "clhs whatever". CLHS stands for "Common Lisp HyperSpec", which
is the hyperlinked, HTML version of the spec.
(Some people will tell you to learn the language by just reading the spec. That's
ridiculous — it's like trying to learn French by reading a dictionary. It's
a useful tool to have, but not the only one you'll need.)
[book-pcl]: http://www.gigamonkeys.com/book/
[clhs]: http://www.lispworks.com/documentation/lw70/CLHS/Front/Contents.htm
### Make Something
Once you've got those two books under your belt and some practice using the
spec, it's time to make something without someone holding your hand. It doesn't
have to be anything big or special, the goal is to just write some Lisp without
having the answer on the next page.
If you need some ideas:
* Do some [Project Euler](https://projecteuler.net/) problems.
* Do some [Advent of Code](https://adventofcode.com/) exercises.
* Make a [stupid Twitter bot](https://twitter.com/git_commands).
* Make a personal calendar program that records your appointments, checks the
weather forecast the day of, etc.
It doesn't really matter what you make, just make *something* on your own.
### Lisp as a System
At this point it's time to take your Common Lisp skills up a notch. Up until
now I've told you to just use any text editor because it's more important to get
you some experience with the language, but now it's time to move on.
In most languages the development process looks something like this:
1. Edit some code in the project with an editor.
2. Compile the project (some languages skip this step).
3. Run the project (or the tests).
4. Observe the output (in the console, a browser, etc).
5. Go to 1.
This is not how most Common Lisp users interact with the language. In Common
Lisp, the development cycle looks more like this:
1. Start a Lisp process.
2. Load the project.
3. Edit some code with your editor.
4. Tell the running process to compile *only the code you edited*.
5. Interact with the changed code in the process via the REPL, an HTTP request, etc.
6. Observe the output (either in the console, a browser, etc).
7. Go to 3.
When you embrace the Lisp way of working you'll rarely recompile and reload an
entire project. Usually you'll write a function (or a macro, or parameter, or
whatever), compile just that function, maybe poke at it in the REPL a bit, and
then move on to the next function. This has some advantages over the
traditional compile-everything-then-run approach.
First: compiling a small chunk of code is fast. I just timed compiling a few of
the larger functions in one of my projects and they took around 50-80
microseconds. You don't have to wait for the compiler, so your
concentration/thought process never has time to wander.
Another advantage is that when you get back the results of your compilation,
any errors or warnings you receive are almost certainly related to the few
lines of code you just compiled. If you compile a ten-line function, run it,
and get a division by zero error you can immediately focus in on the ten lines
you just compiled and think about what changed.
Because the Lisp process is always running, as soon as you compile a function
it's ready to be used in the REPL. You can throw some arbitrary data at it and
inspect the results to see how it behaves in isolation before you build more
things on top of it.
This cycle of making a function, compiling it, poking at it to make sure it's
working as expected, and moving on happens constantly.
In contrast, when working in languages like Scala or Python I almost never find
myself writing one single function and compiling or running the project
immediately. Spinning up the compiler or running the unit tests takes at
*least* a second or two (or, sometimes *minutes* in Scala, unfortunately) so to
avoid having a constant stream of gaps in my thought I end up writing a bunch of
functions at once, and then run the project or tests once I know they have
a chance of working.
But then when I get back an error I have much more surface area to check,
because I've added a lot of new code! So now I have to track down a problem
that might be in something I wrote four minutes ago, whereas in Lisp I would
only have to ever look at the code I wrote in the last few seconds.
I've started using IntelliJ with Scala to help make this a bit less painful. It
does help with the compile times because it recompiles things on the fly, but it
doesn't solve the rest of the problem. I can write a Scala function in IntelliJ
and it will be compiled immediately, but I can't *interact* with it immediately
like I can in Common Lisp.
When you work in this style with Common Lisp I think you'll really grow to love
it. Writing in other languages will begin to feel like shipping your code off
to the DMV and getting it back a week with a page full of red ink somewhere in
the hundred forms you filled out. Writing in Common Lisp feels like interacting
with a living, breathing organism, or like [teaching things to an eager
assistant][eager].
This goes beyond just the short feedback loop and interactive REPL, too. As an
example, imagine you're making a video game and have a bug somewhere in your
damage calculation that will occasionally cause a division by zero. Now let's
say you're working on the code for a particular quest. You'll start the game,
load a save file at the beginning of the quest, and start going through the
steps. All of a sudden, in the middle of killing the final monster for the
quest, you hit the damage bug! In tradition languages, one of two things might
happen:
1. The game crashes, and you get a stack trace and maybe a core dump.
2. You've wrapped a `try` block around the main game loop that logs a stack
trace and ignores errors and allows the game to continue.
Case 1 is pretty bad. You've got to try to track down the bug from a snapshot
of what things looked like at the time (the stack trace and core dump). And
even if you manage to fix it, now you've got to redo all that playing to get
back to testing your quest code that you were originally working on.
Case 2 is bad, in a different way. If you just ignore errors all the time, the
game might now be in a weird state. You also might lose some critical context
that's necessary to debug the problem, unless you're also saving a core dump
(but I don't know of many people who do that).
In Common Lisp you can certainly choose to panic or ignore on errors, but
there's a better way to work. When an error is signaled in Common Lisp, it
doesn't unwind the stack. The Lisp process will pause execution at that point
and open a window in your editor showing you the stack trace. Your warrior's
sword is hovering at the monster, waiting for you. At this point you can
communicate with the running process at the REPL to see what's going on. You
can examine variables in the stack.
Once you figure out the problem ("Oh, I see, the `calculate-armor-percentage`
function returns `0` if a shielding spell ran out during the same frame") you
can fix the code, recompile the problematic function, and *restart the execution
of that function in call the stack*. Your warrior's sword lands, and you move
back to what you were doing before.
You don't have to track down the bug from just a stack trace, like a detective
trying to piece together what happened by the blood stains on the wall. You can
examine the crime *as it's happening* and intervene to save the victim. It's
like if you could run your code in a debugger with a breakpoint at every single
line.
Maybe you don't make video games. But this process can be useful in call kinds
of contexts. Maybe you're writing a web app that talks to an API somewhere, and
are debugging a requests that fails somewhere between two calls to the API,
maybe "create widget `foo`" and "add `foo` to widget list `bar`". Instead of
just aborting the request, logging a stack trace, and now leaving things in
a possibly weird state (`foo` having been created without being in the expected `bar`
list), you can fix the problem and allow the request to finish properly.
Of course this won't always work. If you've got a big function that does some
side effects and then crashes, restarting execution of the function would make
the side effects happen again. But if you divide up your functions well ([one
function to a function][1ftaf]) this case is pretty rare. And even when it does
happen, it just means you're back in the same situation you're in *by default*
with other languages!
[eager]: https://www.reddit.com/r/lisp/comments/4oo1cp/common_lisp_for_clojure_programmer/d4eec68/
[1ftaf]: https://groups.google.com/forum/message/raw?msg=comp.lang.lisp/9SKZ5YJUmBg/Fj05OZQomzIJ
### Learning Paradigms
### Recipes for Success
## Where to Go From Here
### Macros
### Object-Oriented Programming with CLOS
### Low-Level Programming
### Web Development
### Game Development
### Window Management
### Unit Testing
## Modern Common Lisp
### Structure and Building
#### Packages
#### Systems
#### Projects
#### Naming Conventions
### Common/Important Libraries
[Alexandria][alexandria]
[Drakma][]
[Bordeaux Threads][bt]
[Flexi Streams][flexi]
[Gray Streams][gray]
[Iterate][iterate]
[local-time][]
[lparallel][]
[named-readtables][]
[CL-PPCRE][ppcre]
[Roswell][]
[SERIES][series]
Trivial-whatever
[uiop][]
[usocket][]
[alexandria]: https://common-lisp.net/project/alexandria/
[iterate]: https://common-lisp.net/project/iterate/
[series]: https://www.cliki.net/Series
[bt]: https://common-lisp.net/project/bordeaux-threads/
[gray]: https://www.cliki.net/Gray%20streams
[flexi]: https://edicl.github.io/flexi-streams/
[ppcre]: https://edicl.github.io/cl-ppcre/
[uiop]: https://www.cliki.net/UIOP
[usocket]: https://common-lisp.net/project/usocket/
[drakma]: https://edicl.github.io/drakma/
[lparallel]: https://lparallel.org/
[local-time]: https://common-lisp.net/project/local-time/
[named-readtables]: https://github.com/melisgl/named-readtables
[Roswell]: https://github.com/roswell/roswell