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:
MY-PACKAGE:FOO
will work ifFOO
has been exported fromMY-PACKAGE
.MY-PACKAGE::BAR
is needed ifBAR
has not been exported fromMY-PACKAGE
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:
~/common-lisp/
~/.local/share/common-lisp/source/
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/
.