Common Lisp — Reference
Source: http://www.lispworks.com/documentation/HyperSpec/Front/index.htm
Common Lisp
- Created: 1984 (designed 1981–1984 by a committee unifying MacLisp, Zetalisp, Spice Lisp, NIL); standardized as ANSI X3.226-1994 (still the current spec).
- Latest stable: No language version — the standard is frozen since 1994. Implementations evolve independently. Reference implementation (de facto): SBCL 2.6.4 (2026-04-29).
- Paradigms: Multi-paradigm — functional, imperative, object-oriented (CLOS, the most powerful OO in mainstream use), reflective, metalinguistic.
- Typing: Dynamic and strong, with optional inline declarations (
(declare (type fixnum x))) used by SBCL for native unboxed compilation. - Memory: Garbage collected (each implementation has its own GC — SBCL: generational copying with marked pages; CCL: ephemeral generational).
- Compilation: Compiled (typically AOT to native machine code per function); supports interactive incremental compilation. SBCL emits real native amd64/arm64/aarch64 code.
- Primary domains: Symbolic AI (Cyc), expert systems, scientific computing (Maxima — CAS), genomics (NCBI’s GENBANK), aviation scheduling (ITA Software → Google Flights), animation (Naughty Dog GOAL/GOOL), exploration (NASA Deep Space 1), trading systems, language research.
- Notable implementations: SBCL (Steel Bank — fastest open-source, the de facto choice), CCL (Clozure CL — fast macOS focus), ECL (Embeddable CL — compiles to C), CLISP (bytecode, GPL), ABCL (on JVM), Allegro CL (Franz, commercial), LispWorks (commercial, GUI).
- Official docs: Common Lisp HyperSpec — http://www.lispworks.com/documentation/HyperSpec/Front/index.htm
At a glance
Common Lisp is a standardized Lisp from the era when standards meant “everything you’ll ever need,” and the standard is still useful today without amendment. CLOS (Common Lisp Object System) provides multi-method dispatch, multiple inheritance, method combinations, and a metaobject protocol (MOP) that lets you reshape the object system from inside it. The condition system is the gold standard for resumable error handling. Image-based development: you save the running Lisp as a binary (save-lisp-and-die) and resume it. Quicklisp made library distribution painless circa 2010, single-handedly reviving the ecosystem.
Getting started
Install — Get SBCL from https://www.sbcl.org/ (or apt install sbcl, brew install sbcl, pacman -S sbcl). Bootstrap Quicklisp (the package manager):
curl -O https://beta.quicklisp.org/quicklisp.lisp
sbcl --load quicklisp.lisp
* (quicklisp-quickstart:install)
* (ql:add-to-init-file)Editor: Emacs + SLIME (https://slime.common-lisp.dev/) is canonical; SLY is the modernized fork; Sublime Text + SublimeText-SLY, VS Code + Alive, Atom + Slima, Vim + Slimv. Or use Portacle (https://portacle.github.io/) — Emacs+SBCL+SLIME+Quicklisp in one zip.
Hello world:
;; hello.lisp
(format t "Hello, world!~%")Run: sbcl --script hello.lisp. From REPL: (load "hello.lisp").
Project layout — ASDF (Another System Definition Facility) is the build/dependency manager (ships with SBCL/CCL/ECL/etc.):
my-project/
my-project.asd ; defsystem
src/package.lisp ; defpackage
src/main.lisp
tests/main-test.lisp
my-project.asd:
(asdf:defsystem #:my-project
:version "0.1.0"
:depends-on (#:alexandria #:str)
:components ((:module "src"
:components ((:file "package")
(:file "main" :depends-on ("package"))))))Package manager — Quicklisp (Zach Beane) — (ql:quickload :alexandria) fetches + loads. Modern alternatives: Qlot (lockfiles à la Bundler/npm), CLPM (transactional), Ultralisp (rolling-release distribution layered on Quicklisp).
REPL — sbcl for raw, slime/sly from Emacs for the production experience: inline eval (C-x C-e), M-. to definition, M-, back, autocompletion, in-REPL macroexpansion (C-c RET), inspect any value (C-c I), debugger steps with restarts.
Basics
Types/literals — Numbers form a tower like Scheme: integers (arbitrary precision), ratios 1/3, floats 1.0/1.0d0/1.0s0, complex #C(1 2). Characters #\a, #\Space, #\Newline. Strings "abc". Symbols foo, :keyword, cl-user::internal. Lists '(1 2 3), vectors #(1 2 3), arrays #2A((1 2) (3 4)), hash tables ((make-hash-table)), pathnames #P"/tmp/x". Booleans: nil is false (and the empty list); t is canonical true; everything else is true.
Variables/scoping — defvar / defparameter create globals (the latter resets on reload). let / let* for lexical locals. Dynamic scope: any symbol named with *earmuffs* and declared defvar is special — (let ((*foo* 1)) ...) rebinds dynamically inside the dynamic extent. setq/setf mutate. flet/labels for local functions (labels allows recursion).
Control flow — if, when, unless, cond, case, ccase, ecase, typecase. Loops: dotimes, dolist, the kitchen-sink loop macro:
(loop for i from 0 below 10
when (oddp i)
collect (* i i))Pattern-matching is not in ANSI; use trivia (https://github.com/guicho271828/trivia) or optima.
Functions — defun. Lambda lists support &optional, &rest, &key, &aux:
(defun greet (name &key (greeting "Hello") &rest extras)
(format nil "~A, ~A~@[ (~{~A~^, ~})~]" greeting name extras))Functions are objects: #'foo is the function-cell value; call indirectly with funcall or apply. Functions and values live in separate namespaces (Lisp-2): (let ((list 1)) (list list)) returns (1). flet/labels for local funs.
Strings & interpolation — format is the canonical printer (full mini-language: ~A aesthetic, ~S machine-readable, ~D decimal, ~,2F 2-decimal float, ~{~A~^, ~} iterate, ~:R Roman numeral, etc.). No literal interpolation; some libs (cl-interpol) add #?"...".
Collections — Lists (cons cells, slow random access), vectors (1-D arrays), arrays (multi-dim), hash tables (gethash, setf gethash, maphash), structures (defstruct), CLOS classes. alexandria library fills stdlib gaps (e.g., hash-table-keys, mappend, flatten).
Intermediate
Type system depth — Dynamic, but with optional declarations:
(declaim (ftype (function (fixnum fixnum) fixnum) add))
(defun add (x y)
(declare (type fixnum x y) (optimize (speed 3) (safety 0)))
(+ x y))SBCL uses these to omit checks and emit unboxed arithmetic. Types are first-class predicates: (typep x 'integer), (check-type x string). Subtypes with subtypep. Compound types: (integer 0 100), (or string null), (simple-array double-float (*)). Custom: deftype. SBCL also does type inference and warns on inferred-incompatible code.
Modules — Common Lisp has packages (namespaces of symbols), not modules. defpackage:
(defpackage #:my-app
(:use #:cl #:alexandria)
(:export #:run))
(in-package #:my-app)Visibility via :export; access internals with pkg::sym (double colon) vs pkg:sym (single, exported only). ASDF layers compilation/load semantics on top.
Error handling — the condition system — Three concepts: conditions (objects representing notable situations — errors, warnings, info), handlers (handler-case, handler-bind — catch by type), restarts (named recovery strategies established by code that signaled). The signaler can offer (restart-case (signal err) (use-value (v) v)); a handler higher up may invoke (invoke-restart 'use-value 42) and execution resumes at the restart, not at the handler. This is far more powerful than try/catch — interactive debuggers expose restarts as menu items.
Concurrency primitives — Not in ANSI. Bordeaux-Threads (https://github.com/sionescu/bordeaux-threads) is the portability shim: bt:make-thread, bt:make-lock, condition variables. SBCL has native threads (sb-thread) on most platforms. lparallel (https://lparallel.org/) adds futures, promises, parallel map/reduce, work-stealing. chanl for CSP-style channels.
File I/O & networking — (with-open-file (s "path" :direction :input) (read-line s)). Streams are Gray streams (an extensible CLOS-based stream protocol). Networking: usocket (portable), flexi-streams (text decoding), drakma (HTTP client), dexador (modern HTTP), woo / clack (HTTP server stack).
Stdlib highlights — Built-in: cl:format, cl:loop, cl:read/cl:write (the printer-reader pair), cl:sort, cl:reduce, cl:map, cl:make-hash-table, cl:assoc, cl:get-decoded-time, cl:disassemble. De-facto-stdlib libraries (always installed): alexandria (utilities), serapeum (more utilities), str (string ops), iterate (saner loop), trivia (pattern matching), uiop (the OS-portability layer bundled with ASDF).
Advanced
Memory model & GC — Implementation-specific. SBCL uses a generational copying collector with mark-sweep large-object pages. Tune via sb-ext:bytes-consed-between-gcs. (room t) reports allocation; (gc) triggers collection. CCL has a tunable ephemeral generational GC (ccl::*ephemeral-area-size*).
Concurrency deep dive — SBCL’s threads map 1:1 to OS threads. sb-thread:make-thread, sb-thread:make-mutex, sb-thread:condition-wait. Atomics: sb-ext:atomic-incf, cas (compare-and-swap on simple cells). lparallel’s kernel is a work-stealing pool. CCL has ccl:process-run-function. For lock-free CSP, chanl.
FFI/interop — CFFI (Common Foreign Function Interface, https://cffi.common-lisp.dev/) is the portable choice — works across SBCL/CCL/ECL/Allegro/etc.:
(cffi:defcfun ("strlen" c-strlen) :size (s :string))
(c-strlen "hello") ; => 5Each implementation also has a native FFI (sb-alien in SBCL, ccl:%ff-call in CCL). UFFI is the older predecessor (largely superseded). Embeddable Common Lisp (ECL) compiles Lisp to C, which makes embedding into C apps trivial.
Reflection — Pervasive. function-lambda-expression, describe, inspect, documentation, apropos. find-symbol/intern, do-symbols walk packages. CLOS introspection via the MOP: class-of, class-precedence-list, class-slots, slot-definition-name, compute-applicable-methods. SBCL adds sb-introspect:function-lambda-list, sb-introspect:find-definition-source.
Performance tools — (time expr) reports time + consing. (disassemble #'foo) shows native code (real assembly on SBCL). statistical profiler: sb-sprof:with-profiling. deterministic profiler: sb-profile:profile. Heap inspection: (room t). SBCL’s compiler emits note/warning lines for missed inlining/typing — read them.
God mode
CLOS — multi-method dispatch + MOP — Methods dispatch on the runtime types of all required arguments (true multiple dispatch, unlike Java/C++/Python’s single):
(defgeneric collide (a b))
(defmethod collide ((a asteroid) (b ship)) ...)
(defmethod collide ((a ship) (b asteroid)) ...)Method combinations: :before, :after, :around modify the primary method’s call. (call-next-method) is super. Custom combinations via define-method-combination. Multiple inheritance with deterministic linearization (C3-like). MOP (the AMOP book, Kiczales et al. 1991): the object system is itself objects — you can subclass standard-class, override compute-effective-slot-definition, intercept slot-value-using-class, write your own metaclass. This is how persistence, RPC proxies, and ORMs are built. AMOP is the canonical text.
defmacro — code is data — Macros run at compile time, transforming forms. , unquote, ,@ splice, ` quasi-quote, gensym for hygiene-by-construction (Common Lisp is not hygienic — you mint unique symbols manually):
(defmacro with-gensyms ((&rest names) &body body)
`(let ,(loop for n in names collect `(,n (gensym)))
,@body))
(defmacro unless* (test &body body)
(with-gensyms (t-val)
`(let ((,t-val ,test))
(if ,t-val nil (progn ,@body)))))define-compiler-macro runs at compile time on already-validated calls, returning either an optimized form or the original (signalled by returning &whole).
Reader macros & dispatch macros — The reader (the part that turns characters → s-expressions) is itself programmable. set-macro-character rebinds a single character; set-dispatch-macro-character rebinds #X syntaxes. JSON-like literal syntax, infix math (#?[1 + 2]), embedded SQL — all done at the reader level. *readtable* is dynamic; (let ((*readtable* (copy-readtable))) ...) for scoped reader changes. named-readtables library tames namespace pollution.
Restart system in depth — Beyond handler-case/handler-bind: programs can be resumed. A library that signals an error can offer multiple restarts; the caller (or an interactive user at the SLIME debugger) chooses one and execution continues from the restart point. Used in production: file-loaders that hit a bad record can (invoke-restart 'skip-record) and continue. No equivalent in any mainstream language.
Optimization declarations + disassemble — (declare (optimize (speed 3) (safety 0) (debug 0) (space 0) (compilation-speed 0))) per function. (disassemble #'foo) on SBCL shows real x86-64/AArch64 assembly — invaluable for verifying that types eliminated boxing. Type declarations are contracts the compiler trusts at safety 0 — wrong declarations crash.
SBCL-specific extensions — sb-ext (extensions: save-lisp-and-die, compare-and-swap, weak references, finalizers, *posix-argv*), sb-introspect (function metadata, source locations), sb-mop (MOP — same as Closer-MOP exposes portably), sb-sprof (statistical profiler), sb-cover (test coverage), sb-aclrepl (Allegro-compatible REPL), sb-debug (advanced debugging).
ASDF systems + Quicklisp — asdf:defsystem declares a system: components, dependencies, source-file order, perform methods (:test-op). (ql:quickload :system) resolves transitive deps from the Quicklisp dist (a monthly-snapshotted set of ~3000 known-working libs). Lock to a dist date with (ql:dist-version "quicklisp"). Qlot for per-project lockfiles.
FFI: CFFI / sb-alien / CCL FFI — CFFI is portable. sb-alien is SBCL’s native: define-alien-routine, alien-funcall, full struct/union/typedef, supports inline assembly stubs. CCL’s ccl:%ff-call is even thinner. For complex C++: ECL can directly call C++ since it compiles Lisp to C; for SBCL use a C shim.
Image-based development + save-lisp-and-die — Develop interactively at the REPL, defining functions, loading libraries, running tests, fixing things — then (sb-ext:save-lisp-and-die "myapp" :toplevel #'main :executable t) writes the entire heap to a binary that boots back to exactly that state. No build pipeline. No “cold start”. Compiled apps are large (50–100MB — the SBCL runtime + libs) but launch in milliseconds. The Lisp Machine model.
define-symbol-macro & symbol-macrolet — A symbol expands to a form on every reference. Used for fancy slot accessors, “place” abstractions, lexical equivalents of dynamic vars without overhead.
Idioms & style
- Naming:
kebab-casefor symbols (make-foo,do-bar),*earmuffs*for special vars,+constants+for constants,%internalfor “private”, trailingpor-pfor predicates (zerop,string-equal-p). - Formatter: lisp-format / Emacs SLIME’s auto-indent (canonical — there’s no separate formatter; the editor knows the indentation rules). trivial-formatter exists.
- Linter: sblint (wraps SBCL’s compiler warnings as a CLI), lisp-critic, shinmera/parachute for tests. SBCL’s compiler is itself a stringent type-checker if you read its
style-warnings. - Idiomatic: prefer
loopfor complex iteration,mapcar/reducefor the simple cases (or iterate for sanity); use CLOS generic functions instead ofcond-on-type; export throughdefpackage, never via internal mutation; lean on the condition system for resumable workflows;formatfor non-trivial output;with-…macros for resource scoping. - Reviewer focus: missing
gensymin macros (variable capture); unhygienic templates; mutating literals ((nconc '(1 2) '(3))is undefined); usingeqfor value comparison; over-usingsetf(Lisp’s not Java); package conflicts; ignoring SBCL’s compile-time type warnings.
Ecosystem
- Web: Hunchentoot (mature server), Clack (WSGI/Rack-style abstraction), Caveman2/Lucerne (frameworks on Clack), Woo (libev-based, fast), Snooze, CL-Who / Spinneret (HTML DSL), Lass (CSS DSL), Parenscript (Lisp → JS).
- DB: postmodern (Postgres, native protocol), cl-mysql, cl-dbi (driver layer like JDBC), mito (ORM on cl-dbi), clsql (older), CL-Redis.
- Numerical/scientific: Maxima (CAS — written in CL!), lla (BLAS/LAPACK), GSLL (GNU Scientific Lib), mgl (machine learning).
- GUI: CommonQt (Qt4 binding), mcclim (CLIM — Common Lisp Interface Manager, the storied window system), ltk (Tk).
- Testing: fiveam (the popular choice), parachute (modern), prove (pretty output), cl-quickcheck / cl-quickcheck-ish via cl-test-more.
- Docs: Sphinx-style: mgl-pax (Marco Antoniotti’s), codex, declt. Or in-source
(documentation 'foo 'function). - Notable users/projects: ITA Software (airline ops, sold to Google → Google Flights), Grammarly (NLP backend, partly), Naughty Dog (Crash Bandicoot, Jak & Daxter via the GOAL/GOOL languages built on CL), Reddit (originally CL, switched to Python), Cyc (largest knowledge base in CL), Mirai (3D animation — used for Gollum), Boeing, Cisco, Hacker News (Arc — a Lisp dialect implemented in CL).
Gotchas
- Lisp-2 namespaces —
foo(value cell) and#'foo(function cell) are different.(funcall foo)calls a function-valued variable;(foo)calls the function namedfoo. setfis the universal assignment — not just for variables;(setf (gethash :k h) v),(setf (aref a 3) v). Place macros (define-setf-expander) make any expression assignable.- Macros are non-hygienic — manually
gensymevery introduced binding. Variable capture is silent. nilis many things — false, empty list, the symbolnil, the empty type.(eq nil '())ist. Confusing for typed-language refugees.copy-listvscopy-treevscopy-seq— different depths.copy-listshallow;copy-treerecursively copies cons cells;copy-seqfor vectors.- Reader case — by default the reader uppercases all unescaped symbols.
'fooreads as the symbolFOO.(setf (readtable-case *readtable*) :preserve)to keep case (or use|Foo|). equalvsequalpvseqvseql—eq(identity),eql(numbers/chars + eq),equal(recursive on lists/strings, case-sensitive),equalp(also for arrays/hashes, case-insensitive on strings, type-coercive on numbers).loopis its own language — opaque to newcomers. Useiteratefor an extensible Lispy alternative.- No tail-call guarantee in spec — most implementations TCO at high optimize-speed/low-debug declarations, but the standard doesn’t promise. Idiomatic CL uses
loop/dofor iteration. - Special variables surprise — declaring a name with
defvarmakes it special globally, including in anyletbinding of that name (suddenly dynamic). Convention: only*starred*names getdefvar/defparameter. - Reader macros change global state —
set-macro-charactermutates the readtable;let-bind*readtable*to scope. - Image bloat —
save-lisp-and-dieimages include everything youquickload-ed. Use:purify t(where supported) or build minimal images. ASDFsource registry —~/.config/common-lisp/source-registry.conf.d/*and~/quicklisp/local-projects/are the standard discovery paths. Forget that, yourdefsystem“won’t be found”.
Citations
- Common Lisp HyperSpec (the ANSI standard, hosted by LispWorks): http://www.lispworks.com/documentation/HyperSpec/Front/index.htm
- Practical Common Lisp (Peter Seibel, free online): https://gigamonkeys.com/book/
- On Lisp (Paul Graham, free PDF): https://paulgraham.com/onlisp.html
- The Art of the Metaobject Protocol (Kiczales/Rivières/Bobrow, 1991): https://mitpress.mit.edu/9780262610742/the-art-of-the-metaobject-protocol/
- Common Lisp the Language, 2nd Ed (Steele, free): https://www.cs.cmu.edu/Groups/AI/html/cltl/cltl2.html
- SBCL: https://www.sbcl.org/ • manual: http://www.sbcl.org/manual/
- CCL: https://ccl.clozure.com/
- ECL: https://ecl.common-lisp.dev/
- Quicklisp: https://www.quicklisp.org/
- ASDF: https://asdf.common-lisp.dev/
- CFFI: https://cffi.common-lisp.dev/
- Alexandria: https://alexandria.common-lisp.dev/
- Bordeaux-Threads: https://github.com/sionescu/bordeaux-threads
- lparallel: https://lparallel.org/
- SLIME: https://slime.common-lisp.dev/
- SLY: https://github.com/joaotavora/sly
- Portacle: https://portacle.github.io/
- Awesome Common Lisp: https://github.com/CodyReichert/awesome-cl
- ITA Software (Carl de Marcken on CL): https://www.youtube.com/watch?v=v7tt4_NM6T0
- “Beating the Averages” (Paul Graham, on Viaweb in CL): https://paulgraham.com/avg.html