
A Road to Common Lisp - stevelosh
http://stevelosh.com/blog/2018/08/a-road-to-common-lisp/
======
tumba

      > My advice is this: 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.
    

I have found this to be true in my own experience. The perception of
stagnation is, however, a common initial objection to folks working in CL for
the first time.

~~~
mike_ivanov
My personal problem with CL libraries is not that, but rather the lack of
documentation. More often than not, there is no documentation at all, not even
a readme file.. It feels like some library authors simply don't care. I'd say
this attitude has a negative impact on how people perceive viability of those
libraries -- and by extension, of the language.

~~~
armitron
A lot of libraries that don't have separate documentation in the form of
HTML/PDF/README.. are actually well-documented at source level in the form of
docstrings.

Since Common Lisp is an interactive programming language and is meant to be
used interactively (think Smalltalk, not Python) it is common practice to
(interactively) load a library in a Common Lisp image and explore it
(interactively). One can see what symbols are exported from packages, what
these symbols are used for and (interactively) retrieve their documentation.
All of this takes place within the editing environment (ideally Emacs) in a
rapid feedback loop (again think Smalltalk, not Python) that feels seamless
and tremendously empowering.

------
jordigh
In Freenode's #mercurial, sjl said this about common lisp:

    
    
       <sjl_>	CL code from the early 90's runs just fine on SBCL from a month ago
       <sjl_>	But trying to run five-year-old python/ruby/scala makes me hate life.
    

This echoes his earlier blog post, volatile software:

[http://stevelosh.com/blog/2012/04/volatile-
software/](http://stevelosh.com/blog/2012/04/volatile-software/)

I kind of have the feeling that fighting bitrot is sjl's main motivation for
CL.

~~~
diaz
It's a worthy effort. It really tires you after a while with languages that
constantly break the language, the libraries, the ecosystem and the tooling in
the ao called "minor versions". Ain't nobody got time to deal with that stuff.
Then they are surprised that people still run "ancient" versions of compilers
or libraries. Yes scala it's about you.

~~~
hawkice
I stopped using Hugo for this reason. Some point release broke my ability to
rebuild my site, and I was just donezo. Better to write like 100 lines of go
to write the files myself.

------
bunderbunder
I haven't read the whole thing, but, I gotta say, seeing the "Ugliness"
section ([http://stevelosh.com/blog/2018/08/a-road-to-common-
lisp/#ugl...](http://stevelosh.com/blog/2018/08/a-road-to-common-
lisp/#ugliness)) in the preamble gives me high hope for this writeup. It's so
lovely to see writing on Lisp that isn't just hagiography.

~~~
Syzygies
Yes. I don't mind the parens (syntax highlighting can subdue them), but every
time I actually fire up Common Lisp to try it, I see all caps somewhere, like
someone even older than me is shouting at me, or I stumbled across a box of my
Fortran punched cards from before I switched to APL in the 1970's. That's an
ugliness not addressed by this guide.

~~~
lisper
There is a good reason for this "ugliness": it makes it easier to distinguish
the things that you typed in vs the things that Lisp printed out. Nowadays we
can make these distinctions with fonts, but back in the teletype days this was
a really useful feature. Yes, the teletype days are over, but teriminal.app
still exists and there are situations where interacting with Lisp directly
through a terminal is useful, and so this feature can still be useful even
today. Like the parens, it takes a little getting used to, but once you're
used to it, you'll miss it when it's not there.

~~~
pjmlp
Yes, the same reason why all Wirth languages use uppercase for keywords.

And not a big deal on the age of automatic code formatters.

------
krmboya
Thanks for this guide, I'll keep referring to it often.

My current _road_ to Common Lisp is working through Peter Norvig's book
_Paradigms of Artificial Intelligence Programming_ [0]. It's not a direct
route to the kind of programming most people do nowadays, but I hope to at
least get a taste of what it was to be a researcher in classical AI.

[0] [https://github.com/norvig/paip-lisp](https://github.com/norvig/paip-lisp)

~~~
nextos
Not only classical AI. If you bake in probability into logic-based Lisp &
Prolog AI you end up in probabilistic programming. This is a very hot research
topic.

You can also then add deep-learning based samplers. It's all connected.

~~~
agumonkey
as in this
[https://www.cs.cornell.edu/courses/cs4110/2016fa/lectures/le...](https://www.cs.cornell.edu/courses/cs4110/2016fa/lectures/lecture33.html)

------
TurboHaskal
This is a godsend. Probably the best introductory article on Lisp to date.

------
vtail
I'm wondering what is people's opinion on modern Scheme dialects like Racket
vs. Common Lisp? It seems to be pretty stable, with active development
community, and comes with batteries installed.

~~~
eadmund
Well, first off Racket is a different language from Scheme, although it's very
similar. It's definitely very cool, and the folks involved have invested a
tremendous amount of effort into it.

At the end of the day, though, I prefer Lisp. I like that it's standarised; I
like that so much code runs in just about every implementation; I like that —
as someone noted elsethread — in Lisp it's not uncommon for libraries to be
_done_.

I like that Lisp is much more _complete_ than Scheme. Standarised places are
great. Standardised extensible types are wonderful. I _don 't_ like that so
many in the Scheme community are so very opposed to adding to the language, no
matter how painful the lack (witness the abject failure of R6RS).

CLOS is amazingly good, better than any object system in any other language
I've used. Scheme doesn't have a standard version.

I think that multiple namespaces is a huge feature. A lot of Schemers
disagree, but I don't see a good reason for functions, macros, classes, tags
&c. to share a namespace, and it makes programs more obtuse.

I don't care for Scheme's separate Boolean types, nor for the way it splits
NIL, () & #f. They make code less concise, for no terribly good reason IMHO.
Maybe that's a matter of taste, but I think it reflects the pragmatism of Lisp
vice the idealism of Scheme.

Lisp has standardised compiler macros. Lisp's normal macros are, I believe,
more powerful than Scheme's (as I understand it, one can implement Scheme
macros in Lisp but not Lisp macros in Scheme).

Scheme's dynamic-wind is broken, while UNWIND-PROTECT isn't.

Scheme's continuations in general are really awesome, but make it slightly too
difficult to optimise code. I think it's great to have them available in an
educational language, but not so great to have them in a general-purpose
industrial language meant for real programs.

Generally, when I come across some corner of the Lisp standard I don't
understand, some years later I'll recognise how incredibly valuable it is to
be able to have it, and how great it is that every implementation has it. I
never come across any corner of the Scheme standards, because they have no
corners. Whenever I write Scheme I'm not really writing Scheme — I'm really
writing guile or whatever.

Scheme's a wonderful language for teaching C.S. concepts like continuations &
computer science in general — it's not IMHO a good language for industrial-
strength software.

Racket is a single implementation of what used to be a Scheme but has now
grown to be something else entirely different. It's really cool — I just wish
everyone involved had spent that time on SBCL & portable Common Lisp libraries
instead. It's a free world, of course!

~~~
flavio81
Agree with all points above.

I think the most important plus for CL is that CL is image-based, and that
every important thing is already standardized, either on the ANSI standard, or
de-facto via mature libraries.

~~~
lispm
> CL is image-based

CL is not image based. That's a popular implementation detail.

There are several implementations which are not image based. If we look
closely we would find probably ten CL implementations which are not image-
based of which maybe five are still somewhat in use. The Common Lisp standard
also says absolutely nothing about programs/applications/images/libraries...

A prominent example for a CL implementation which is not image-based is ECL,
Embeddable Common-Lisp:

[https://common-lisp.net/project/ecl/static/manual/ch26.html](https://common-
lisp.net/project/ecl/static/manual/ch26.html)

\--- Traditionally, Common Lisp implemenations have provided a function to
save the dump all data from a running Lisp process into a file. The result was
called the Lisp image and could be shipped to other version compatible
implementations.Nowadays, having less control of the systems it runs in, a
Lisp implementation must work very hard to dump memory images and be able to
load and execute them afterwards.

ECL has chosen to avoid this process entirely. Instead, we conceive five
different portable models for building and shippin your programs. The models,
described in Table 1.1, enumerate the different kinds of files that ECL can
portably produce. To get one or more of the products mentioned in the table,
you may resort to a low level API described in Part III. However, we recommend
a simpler way based on using System Definition Files to describe the structure
of your project and let ECL build the desired target for you. This approach is
described in the following sections. \---

------
foo101
> If you’re like me and already have Vim burned too deeply into your fingers
> to ever get it out, I’d recommend Vim with Vlime.

Anyone has tried both Slimv and Vlime for Vim? What are the differences? Which
one gives an experience closer to that of SLIME?

~~~
Jach
I've been using slimv for hobby hacking for a while, but not professionally so
you can take my commentary with a big grain of salt. You should try both, and
if 'stevelosh likes vlime better you might start with that.

I like slimv a lot better myself, and prior to that I made due with a gnu
screen split-window terminal with a vim plugin that would send stuff from one
screen panel to the other (used that for Python, Clojure, and Node sometimes
too). I tried using vlime somewhat recently, but it just felt off, hard to
express everything I didn't like but maybe the experience of having to launch
your REPL separately was the beginning (slimv just finds your lisp on the
path). You're encouraged to compile whole files at once rather than bit by bit
(perhaps sensible for Real Work), the REPL buffer is read-only which is quite
bizarre to me, and the default key bindings make less sense. Feature-wise it
seemed comparable since they both use Swank. The tutorial at
[https://kovisoft.bitbucket.io/tutorial.html](https://kovisoft.bitbucket.io/tutorial.html)
which follows a classic SLIME demo vid is nicer than the vim-tutor for vlime.

~~~
stevelosh
> the REPL buffer is read-only which is quite bizarre to me

The way I work around this is to run the SBCL process inside a Neovim terminal
split (with rlwrap). That way I get a vanilla SBCL REPL plus the stability of
Vlime.

------
hirow
Just recently searched for an IMAP library but only found one for Allegro
Common Lisp. The lack of libraries is one of my biggest concerns when it comes
to using Lisp in production.

~~~
flavio81
There are two more libs on Cliki:

[https://www.cliki.net/email](https://www.cliki.net/email)

And note that CLiki does not cover all that it's out there.

~~~
fiddlerwoaroof
I know from experience that clonsigna is broken :) I don’t think it would be
difficult to fix, but, as is, it doesn’t work very well.

------
typon
Would you recommend learning Common Lisp over Clojure?

~~~
cultus
Clojure is really your better bet. It is highly practical with a good
community and excellent Java interop. You never have to worry about finding a
good library. The syntax is also a bit more easily parsable than CL. If you
don't touch the Java interop stuff, it's just as elegant as CL.

~~~
typon
I've spent a few months learning Clojure - never gave CL a try. Here are a few
thoughts on Clojure:

1\. Excellent syntax and library support. It is my first Lisp, but I can't how
I programmed without macros and persistent data structures.

2\. I hate the stack traces. I've used both Clojure and Clojurescript (mainly
cljs), and the stack traces for errors are nearly indecipherable. To be fair I
am using React (not Reagent), but I don't find it too much better with other
libraries.

3\. I hate the build system. It is fractured and there are too many mediocre
options. shadow-cljs is the best one I've used, and it works okay not great. I
also hate that I can't distribute standalone binaries. I have used pkg, which
distributes Node project as binaries, but the binaries are huge (something
like 85MB+)

4\. The core functional language is excellent, but protocols, types etc. seem
much more ad-hoc and not well-designed. I'm used to object oriented (Python)
and I find the lack of focus on an object-system a bit unsettling.

I have no clue if Common Lisp fixes these issues or brings new ones.

~~~
socksy
I'm not sure it's fair to bring up a fractured build system when talking about
ClojureScript, and comparing it to Common Lisp, which you would not be
compiling to JavaScript and interop'ing with NodeJS etc.

With (JVM) Clojure, leiningen has to be 90+% of the Clojure projects in the
wild. And in that world, the equivalent to a native binary is an uberjar,
which is rather easy and pretty much standardized.

The stack traces could be improved though.

~~~
lispm
> Common Lisp, which you would not be compiling to JavaScript and interop'ing
> with NodeJS etc.

[https://jscl-project.github.io](https://jscl-project.github.io)

[https://common-lisp.net/project/parenscript/](https://common-
lisp.net/project/parenscript/)

~~~
socksy
That's great, are you using these? How is the interop? How does that compare
with ClojureScript and shadow-cljs? How can you make a single binary?

Maybe I worded wrongly the first time. I definitely didn't mean to imply that
Common Lisp can't do something. That would be as foolish as saying Emacs can't
do something ;)

I was just pointing out that we aren't comparing like-with-like once you bring
in a totally different ecosystem such as JavaScript's (which is historically
neither Common Lisp's nor Clojure's main focus).

~~~
lispm
> That's great, are you using these?

Not often. In Common Lisp I don't have to use them.

------
rwallace
An excellent guide, thanks!

One suggestion: checked again just now and SBCL is still not production-ready
on Windows (for the understandable reason of insufficient volunteers); perhaps
the recommendation for that platform should be changed to CCL?

~~~
flavio81
>One suggestion: checked again just now and SBCL is still not production-ready
on Windows (for the understandable reason of insufficient volunteers); perhaps
the recommendation for that platform should be changed to CCL?

It only has a warning for threading code that has been left there for years.
But I use it on windows with no problems.

On the other hand Clozure CL is a very very good implementation with a
loooooooooooooooooooooooooooooooooooooooooooooooooong history (emphasis added)
being used in production stuff.

But don't limit yourself to SBCL and CCL -- take a look also at ECL, ABCL,
CLASP, etc.

~~~
rwallace
The Windows x86 version of SBCL crashes immediately on startup, so I figured
the warning on the x64 version should be taken seriously, but if you are
saying it is solid after all, that is good news.

Going to give CCL a try.

------
pvaldes
It mades sbcl bearable at least. Not a minor point, so thanks stevelosh.

------
stewbrew
I just wish common lisp people would embrace static typing so that I could ask
the reply/compiler: After all these changes, is the code still formally
correct (as far as types were specified)? Typed racket/clojure show it's
possible.

------
earenndil
I do quite like common lisp, but:

> In Common Lisp you can certainly choose to panic on or ignore 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 over 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, or even run any arbitrary code
> you want.

This doesn't seem like something that's particularly difficult to do with
c/gdb.

~~~
outworlder
> This doesn't seem like something that's particularly difficult to do with
> c/gdb.

Well, why don't you do it, then? Not trying to be snarky(not much anyway), but
I feel you are seriously under-estimating the work involved. Either that, or
the description does not make it clear enough.

It's a full blown REPL. We are not talking about just printing out the code at
that point, or maybe mutating a variable on the stack. We can execute
arbitrary code and even replace entire sections of code with new code, and
then hit continue and it works as if nothing happened, only now running the
new (and hopefully corrected) code. Just the memory management implications of
doing this in C would be very interesting.

Note that you can also do this over a network connection. NASA folks used this
to great effect to patch Deep Space-1. Lisp machines also used this capability
over the entire OS. I have also used a similar mechanism (with Chicken Scheme)
to do incremental development on iOS, without having to do the whole xcode
deploy/run cycle.

The closest analog I can think of today is a browser's "console", which lets
you do similar things for javascript code.

~~~
earenndil
You can put your code in shared objects and then reload them. Well, I've never
done it myself as I haven't seen the need but it seems easy enough, you'd
simply have to define a macro for defining functions (you'd just have to write
them as FUN(void, bla)(int x, int y) instead of void bla(int x, int y)), which
would add the function to a symbol table so then you can say reload("bla")
from within gdb and then keep running. I'm not quite sure what you mean by
'the memory management implications of doing this in C would be very
interesting'? The only thing, really, that you'd expect to stop working static
variables.

~~~
foo101
There are a few ways in which the shared objects method you suggest does not
match the full power of Lisp debugging.

First of all, when a C program crashes, it just crashes. There is no REPL.
There is only a core dump. So any live-debugging you plan to do is after the
fact. After you have seen a crash, you would now begin to prepare for the next
crash by launching your process via GDB or restarting your process and
attaching a GDB to it. Whether a similar crash would occur again or not or
when it would occur again depends on the nature of the bug. Now contrast this
with Lisp debugging when your program crashes, it stops there and offers you
an REPL to interact with the program right then. There is no need to wait for
the next crash.

Secondly, when you debug with GDB, you would be dealing with syntaxes: The
syntax of C that we are so familiar with. The GDB syntax to investigate the
problem that we may be less familiar with. When the Lisp debugger offers the
REPL to you, you are working with Lisp again. Your compiler, debugger,
program, etc. all are part of the same unified environment where you just
execute Lisp code to debug your issue.

Finally, putting your code in shared objects and reloading them requires you
to go through the complete write-build-test-debug cycle. And then what do you
do if your shared object itself crashes? With Lisp you skip the write-build-
test part when all you want to do is debug an error. You jump straight to the
debug part of the cycle and begin investigating the runtime state. And it
works the same in a uniform manner whether your main program crashes or a
dependency crashes.

