Common Lisp Notes

These are some unordered notes I’m taking as reminders to myself about how Common Lisp development works in the modern world. I’ve toyed with Lisp here and there, but never sat down and tried to create a real project with it. I need a place to put my thoughts so as not to forget the random tidbits I learn along the way.

The Language

Uninterned Symbols

When you define a package in Common Lisp, the example you always see is:

(defpackage #:my-package
  (:use :cl))

But if you were to do (defpackage my-package ...) or even (defpackage "my-package"), it would work just as well. Why? What does the #: do?

Apparently the hash mark makes the package name an uninterned symbol. The book “Common Lisp, the Language” has this to say about it (Section 10.3):

An uninterned symbol is a symbol used simply as a data object, with no special cataloguing (it belongs to no particular package). An uninterned symbol is printed as #: followed by its print name.

For example:

CL-USER> (defpackage FOO)
#<PACKAGE "FOO">
CL-USER> (defpackage #:BAR)
#<PACKAGE "BAR">
CL-USER> (find-symbol "FOO")
FOO
:INTERNAL
CL-USER> (find-symbol "BAR")
NIL
NIL
CL-USER>

Using symbols in packages

If you have exported a symbol from a package, you can use a single colon between the name of the package and the name of the symbol. If a symbol is not exported, you can only access it from another package by using two colons between the package name and the symbol name. For example:

Undefining symbols and packages

Sometimes you want a symbol removed. You can do this with makunbound and fmakunbound. You can also delete a whole package with delete-package.

makunbound

Remove

(defvar a)
(setq a 25)
a ;; -> 25
(makunbound 'a)
a ;; -> error! "a" is unbound.

fmakunbound

Remove a function. The argument is a symbol.

(defun square (x) (* x x))
(square 5) ;; -> 25
(fmakunbound 'square)
(square 5) ;; -> error! "square" is unbound

delete-package

CL-USER> (defpackage #:my-package)
CL-USER> (in-package #:my-package)
MY-PACKAGE> (in-package #:cl-user)
CL-USER> (delete-package #:my-package)
CL-USER> (in-package #:my-package) ;; -> error! No package.

Sly

I love Emacs, so I use SBCL + Sly.

Cheatsheet

C-c C-c (M-x sly-compile-defun)
Compile the expression at the cursor.
C-c ~
Sync the REPL to the currently compiled package in the sly buffer. (Note: If you haven't compiled the package, this won't work!)
C-M-x (M-x sly-eval-defun)
Evaluate the current `defun` in Sly
C-c C-d C-d
Show eldoc documentation for the current symbol

Improving the REPL

I almost immediately ran into an issue with the REPL in Sly, where it would insert what seemed like extra carriage returns when it paused for input with a (read-line). It worked fine when NOT running under Sly. It turns out this is a known issue (if you can call it an issue): https://github.com/joaotavora/sly/issues/347

The workaround is in the issue, just add the following to the file ~/.slynkrc:

(setf slynk:*use-dedicated-output-stream* nil)

Using ASDF

ASDF is included in quicklisp. It expects all of your systems to be in one of two common places:

I tend to develop my code under ~/Projects/, just out of ancient habit. I guess I could symlink these into ~/common-lisp/ or ~/.local/share/common-lisp/sources/, but I don’t really like that.

Instead, ASDF lets you define a source-registry. You can configure it through $XDF_CONFIG_DIRS/common-lisp/source-registry.conf, or through files ending with the .conf suffix under $XDF_CONFIG_DIRS/common-lisp/source-registry.conf.d/

For example, you could add individual directories:

(:directory "~/Projects/example/")

This will scan ~/Projcts/example/, but nothing under it.

You can also define entire trees that will be scanned for ASDF project files:

(:tree "~/Projects/common-lisp/")

This will find any projects under ~/Projects/common-lisp/.