Skip to content
Lesson 2 of 7

Step 1 of 5 · Reading · ~2 min

Learn

Macros and Special Forms

Common Lisp has TWO kinds of variable bindings:

Lexical — what a plain let gives you:

(defun inner () counter)     ;; free reference, resolved where INNER is written

(let ((counter 5))
    (inner))                 ;; NOT 5 — the let is invisible to inner

A lexical binding is visible only inside the text of the form that made it. inner was written somewhere else, so the caller's let may as well not exist.

Note what is deliberately absent from that example: defvar. Adding (defvar counter 10) above it would proclaim counter special, and then the very same let rebinds it dynamically and inner returns 5. Same code, opposite answer — which is why you keep the two kinds straight, and why special names wear earmuffs.

Special / dynamic (declared with defvar or defparameter):

(defparameter *count* 10)     ;; *earmuffs* by convention for special vars

(defun inner () *count*)

(let ((*count* 5))             ;; rebinds the special variable
    (inner))                    ;; → 5 — inner sees the dynamic value

The * earmuffs convention marks special variables. Inside a let for a special variable, the binding is on a stack — visible to all callees, restored when the let exits.

declare special to make a single use special:

(let ((x 5))
    (declare (special x))
    (some-function))            ;; some-function sees this x dynamically

Use cases:

Configuration:

(defparameter *log-level* :info)

(defun log-msg (msg)
    (when (member *log-level* '(:debug :info))
        (format t "~a~%" msg)))

(let ((*log-level* :error))
    (log-msg "this won't print"))

Output redirection:

(defparameter *standard-output* ...)

(let ((*standard-output* (make-string-output-stream)))
    (do-something-that-prints)
    (get-output-stream-string *standard-output*))

The whole program's print calls now go to the string stream — without changing any code.

Database connection:

(defparameter *db* nil)

(defun execute (sql)
    (postmodern:query *db* sql))

(let ((*db* (connect ...)))
    (execute "..."))

Each thread / scope can have its own *db* without passing it as an argument.

defvar vs defparameter:

  • defvar — only sets if undefined (preserves existing value across reloads)
  • defparameter — always sets (reloading the file resets the value)

For true constants, use defconstant (signals an error on redefinition with a different value).

Up nextFormat DirectivesFormat and Streams

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…