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 layoutASDF (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 managerQuicklisp (Zach Beane) — (ql:quickload :alexandria) fetches + loads. Modern alternatives: Qlot (lockfiles à la Bundler/npm), CLPM (transactional), Ultralisp (rolling-release distribution layered on Quicklisp).

REPLsbcl 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/scopingdefvar / 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 flowif, 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.

Functionsdefun. 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 & interpolationformat 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/interopCFFI (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")    ; => 5

Each 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 extensionssb-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 + Quicklispasdf: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-case for symbols (make-foo, do-bar), *earmuffs* for special vars, +constants+ for constants, %internal for “private”, trailing p or -p for 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 loop for complex iteration, mapcar/reduce for the simple cases (or iterate for sanity); use CLOS generic functions instead of cond-on-type; export through defpackage, never via internal mutation; lean on the condition system for resumable workflows; format for non-trivial output; with-… macros for resource scoping.
  • Reviewer focus: missing gensym in macros (variable capture); unhygienic templates; mutating literals ((nconc '(1 2) '(3)) is undefined); using eq for value comparison; over-using setf (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 namespacesfoo (value cell) and #'foo (function cell) are different. (funcall foo) calls a function-valued variable; (foo) calls the function named foo.
  • setf is 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 gensym every introduced binding. Variable capture is silent.
  • nil is many things — false, empty list, the symbol nil, the empty type. (eq nil '()) is t. Confusing for typed-language refugees.
  • copy-list vs copy-tree vs copy-seq — different depths. copy-list shallow; copy-tree recursively copies cons cells; copy-seq for vectors.
  • Reader case — by default the reader uppercases all unescaped symbols. 'foo reads as the symbol FOO. (setf (readtable-case *readtable*) :preserve) to keep case (or use |Foo|).
  • equal vs equalp vs eq vs eqleq (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).
  • loop is its own language — opaque to newcomers. Use iterate for 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/do for iteration.
  • Special variables surprise — declaring a name with defvar makes it special globally, including in any let binding of that name (suddenly dynamic). Convention: only *starred* names get defvar/defparameter.
  • Reader macros change global stateset-macro-character mutates the readtable; let-bind *readtable* to scope.
  • Image bloatsave-lisp-and-die images include everything you quickload-ed. Use :purify t (where supported) or build minimal images.
  • ASDF source registry~/.config/common-lisp/source-registry.conf.d/* and ~/quicklisp/local-projects/ are the standard discovery paths. Forget that, your defsystem “won’t be found”.

Citations