Lisp newcomers, I still care about you ;) A section on variables was missing on the Cookbook, here it is.
As usual, this is best read on the Common Lisp Cookbook. This is where it will get updates and fixes.
The Cookbook has many contributors. You can contribute too. I myself mostly contributed (out of frustration) as I was discovering Common Lisp, the language and the ecosystem. It’s been years now, but I still take care of it because I like it, and thanks to your tips. As I don’t have a salary nor a million-dollar company, I do appreciate them. I’m on github sponsors too. Thank you!
Also, I can now generate a good-quality PDF thanks to Typst and Pandoc. Stay tuned.
So, you are writing your first Common Lisp program (again, welcome!) and you want to declare variables. What are your options?
When in doubt, use defparameter
for top-level parameters.
Use let
or let*
for lexical scope:
(let* ((a 2)
(square (* a a)))
(format t "the square of ~a is ~a" a square))
Use setf
to change them.
defparameter
: top-level variablesdefvar
: no redefinition- The “*earmuff*” convention
- Global variables are created in the “dynamic scope”
setf
: change valueslet
,let*
: create lexical scopes- Unbound variables
- Global variables are thread safe
- Addendum:
defconstant
- Guidelines and best practices
defparameter
: top-level variables
Use defparameter
to declare top-level variables, like this:
(defparameter *name* "me")
(defun hello (&optional name)
"Say hello."
(format t "Hello ~a!" (or name *name*)))
defparameter
accepts an optional third argument: the variable’s docstring:
(defparameter *name* "me"
"Default name to say hello to.")
The inline docstrings are an important part of the Common Lisp
interactive experience. You will encounter them during your coding
sessions (and we lispers usually keep our Lisp running for a long
time). In Emacs and Slime, you can ask for a symbol’s docstring with
C-c C-d d
(Alt-x slime-describe-symbol
). You can also ask for a
docstring programmatically:
(documentation '*name* 'variable)
We ask the documentation of the *name*
symbol, not what it holds,
hence the quote in '*name*
(which is short for (quote
*name*)
. Another “doc-type” is 'function
. See: in Common Lisp,
variables and functions live in different “namespaces”, and it shows
here.
We’ll mention the defparameter
form with no value below.
redefining a defparameter
A Common Lisp coding session is usually long-lasting and very interactive. We leave a Lisp running and we interact with it while we work. This is done with Emacs and Slime, Vim, Atom and SLIMA, VSCode and Alive, Lem… and more editors, or from the terminal.
That means that you can do this:
1- write a first defparameter
(defparameter *name* "me")
either write this in the REPL, either write this in a .lisp file and
compile+load it with a shortcut (C-c C-c
(Alt-x slime-compile-defun
) in
Slime on this expression, or C-c C-k
(Alt-x slime-compile-and-load-file
)
to compile and load everything you have in the current buffer). If you
work from a simple terminal REPL, you can (load …)
a .lisp file.
Now the *name*
variable exists in the running image.
2- edit the defparameter line:
(defparameter *name* "you")
and load the changes the same way: either with the REPL, or with a
C-c C-c
. Now, the *name*
variable has a new value, “you”.
A defvar
wouldn’t be redefined.
defvar
: no redefinition
defvar
defines top-level variables and protects them from redefinition.
When you re-load a defvar
, it doesn’t erase the current value, you
must use setf
for this.
(defvar *names-cache* (list)
"Store a list of names we said \"hello\" to.")
(defun hello (&optional (name *name*))
(pushnew name *names-cache* :test #'string-equal)
(format t "hello ~a!" name))
Let’s see it in use:
CL-USER> (hello)
hello you!
NIL
CL-USER> *names-cache*
("you")
CL-USER> (hello "lisper")
hello lisper!
NIL
CL-USER> *names-cache*
("lisper" "you")
What happens to *names-cache*
if you redefine the defvar
line
(with C-c C-c
, or C-c C-k
, or on the REPL…)?
It doesn’t change and that is a good thing.
Indeed, this variable isn’t a user-visible parameter, it doesn’t have
an immediate use, but it is important for the program correctness, or
strength, etc. Imagine it holds the cache of your webserver: you don’t
want to erase it when you load new code. During development, we hit a
lot C-c C-k
to reload the current file, we can as well reload our
running app in production, but there are certain things we want
untouched. If it is a database connection, you don’t want to set it
back to nil, and connect again, everytime you compile your code.
You must use setf
to change a defvar’s variable value.
The “*earmuff*” convention
See how we wrote *name*
in-between “*earmuffs*”. That is an
important convention, that helps you not override top-level variables
in lexical scopes.
(defparameter name "lisper")
;; later…
(let ((name "something else"))
;; ^^^ overrides the top-level name. This will cause bugs.
…)
This becomes a feature only when using earmuffs:
(defparameter *db-name* "db.db")
(defun connect (&optional (db-name *db-name*))
(sqlite:connect db-name))
(let ((*db-name* "another.db"))
(connect))
;;^^^^ its db-name optional parameter, which defaults to *db-name*, now sees "another.db".
By the way, for such a use-case, you will often find with-…
macros
that abstract the let
binding.
(with-db "another.db"
(connect))
By the way again, an earmuff is a thing that covers the ears (but only the ears) in winter. You might have seen it in movies more than in reality. The lasting word is: take care of yourself, stay warm and use earmuffs.
Global variables are created in the “dynamic scope”
Our top-level parameters and variables are created in the so-called
dynamic scope. They can be accessed from anywhere else: from
function definitions (as we did), in let
bindings, etc.
In Lisp, we also say these are dynamic variables or special.
It could also be possible to create one from anywhere by proclaiming it “special”. It really isn’t the thing you do everydays but, you know, in Lisp everything’s possible ;)
A dynamic variable can be referenced outside the dynamic extent of a form that binds it. Such a variable is sometimes called a “global variable” but is still in all respects just a dynamic variable whose binding happens to exist in the global environment rather than in some dynamic environment. [Hyper Spec]
setf
: change values
Any variable can be changed with setf
:
(setf *name* "Alice")
;; => "Alice"
It returns the new value.
Actually, setf
accepts pairs of value, variable:
(setf *name* "Bob"
*db-name* "app.db")
;; => "app.db"
It returned the last value.
What happens if you setf
a variable that wasn’t declared yet? It
generally works but you have a warning:
;; in SBCL 2.5.8
CL-USER> (setf *foo* "foo")
; in: SETF *FOO*
; (SETF CL-USER::*FOO* "foo")
;
; caught WARNING:
; undefined variable: CL-USER::*FOO*
;
; compilation unit finished
; Undefined variable:
; *FOO*
; caught 1 WARNING condition
"foo"
We see the returned “foo”, so it worked. Please declare variables with
defparameter
or defvar
first.
Let’s read the full setf
docstring because it’s interesting:
Takes pairs of arguments like SETQ. The first is a place and the second
is the value that is supposed to go into that place. Returns the last
value. The place argument may be any of the access forms for which SETF
knows a corresponding setting form.
Note that setq
is another macro, but now seldom used, because setf
works on more “places”. You can setf functions and many things.
let
, let*
: create lexical scopes
let
lets you define variables in a limited scope, or override top-level variables temporarily.
Below, our two variables only exist in-between the parenthesis of the let
:
(let* ((a 2)
(square (* a a)))
(format t "the square of ~a is ~a" a square))
;; so far so good
(format t "the value of a is: ~a" a)
;; => ERROR: the variable A is unbound
“unbound” means the variable is bound to nothing, not even to NIL. Its symbol may exist, but it isn’t associated to anything.
Just after the scope formed by the let
, the variables a
and square
don’t exist anymore.
When the Lisp reader reads the format
expression, it reads a a
symbol, which now exists in the global environment, but it isn’t bound.
Food for thought: the fact to write a variable name and have the Lisp reader read it creates the symbol, but doesn’t bind it to anything.
Our two variables can be accessed by any form inside the let
binding. If we
create a second let
, its environment inherits the previous one (we
see variables declared above, fortunately!).
(defparameter *name* "test")
(defun log (square)
(format t "name is ~s and square is ~a" *name* square))
(let* ((a 2)
(square (* a a)))
;; inside first environment
(let ((*name* "inside let"))
;; inside second environment,
;; we access the dynamic scope.
(log square)))
;; => name is "inside let" and square is 4
;; => NIL
(print *name*)
;; => "test"
;; ^^^^ outside the let, back to the dynamic scope's value.
We could also define a function inside a let, so that this function definition “sees” a binding from a surrounding let at compile time. This is a closure and it’s for the chapter on functions.
A “lexical scope” is simply
a scope that is limited to a spatial or textual region within the establishing form. “The names of parameters to a function normally are lexically scoped.” [Hyper Spec]
In other words, the scope of a variable is determined by its position in the source code. It’s today’s best practice. It’s the least surprising way of doing: you can see the scope by looking at the source code.
let
vs let*
By the way, what is the syntax of let
and what is the difference with let*
?
let*
lets you declare variables that depend on each other.
let
’s basic use is to declare a list of variables with no initial
values. They are initialized to nil
:
(let (variable1 variable2 variable3) ;; variables are initialized to nil by default.
;; use them here
…)
;; Example:
(let (a b square)
(setf a 2)
(setf square (* a a))
(list a b square))
;; => (2 NIL 4)
;; exactly the same:
(let (a
b
square)
…)
You can give default values by using “pairs” of elements, as in (a 2)
:
(let ((a 2) ;; <-- initial value
square) ;; <-- no "pair" but still one element: defaults to NIL.
(setf square (* a a))
(list a square))
Yes, there are two ((
in a row! This is the syntax of Common
Lisp. You don’t need to count them. What appears after a let
is
variable definitions. Usually, one per line.
The let’s logic is in the body, with a meaningful indentation. You can read Lisp code based on indentation. If the project you are looking at doesn’t respect that, it is a low quality project.
Observe that we kept square
to nil. We want it to be the square of
a
, so can we do this?
(let ((a 2)
(square (* a a))) ;; WARN:
…)
You can’t do that here, this is the limitation of let
. You need let*
.
You could write two let
s:
(let ((a 2))
(let ((square (* a a)))
(list a square)))
;; => (2 4)
This is equivalent to let*
:
(let* ((a 2)
(square (* a a)))
…)
let
is to declare variables that don’t depend on each other, let*
is to declare variables which are read one after the other and where
one can depend on a previous one.
This is not valid:
(let* ((square (* a a)) ;; WARN!
(a 2))
(list a square))
;; => debugger:
;; The variable A is unbound.
The error message is clear. At the time of reading (square (* a a))
, a
is unknown.
setf inside let
Let’s make it even clearer: you can setf
any value that is
shadowed in a let
binding, once outside the let, the variables are
back to the value of the current environment.
We know this:
(defparameter *name* "test")
(let ((*name* "inside let"))
(format t "*name* inside let: ~s" *name*))
;; => *name* inside let: "inside let"
(format t "*name* outside let: ~s" *name*)
;; => *name* outside let: "test"
we setf a dynamic parameter that was shadowed by a let binding:
(defparameter *name* "test")
(defun change-name ()
;; bad style though,
;; try to not mutate variables inside your functions,
;; but take arguments and return fresh data structures.
(setf *name* "set!"))
;; ^^^^^ from the dynamic environment, or from a let lexical scope.
(let ((*name* "inside let"))
(change-name)
(format t "*name* inside let: ~s" *name*))
;; => *name* inside let: "set!"
(format t "*name* outside let: ~s" *name*)
;; => *name* outside let: "test"
When you don’t use defined variables
Read your compiler’s warnings :)
Below, it tells us that b
is defined but never used. SBCL is pretty
good at giving us useful warnings at compile time (every time you
hit C-c C-c
(compile and load the expression at point), C-c C-k
(the whole file) or use load
).
(let (a b square)
(list a square))
;; =>
; caught STYLE-WARNING:
; The variable B is defined but never used.
This example works in the REPL because SBCL’s REPL always compiles expressions.
This may vary with your implementation.
It’s great to catch typos!
(let* ((a 2)
(square (* a a)))
(list a squale))
;; ^^^ typo
If you compile this in a .lisp file (or in a Alt-x slime-scratch
lisp buffer), you
will have two warnings, and your editor will underline each in two
different colors:
- first, “square” is defined but never used
- second, “squale” is an undefined variable.
If you run the snippet in the REPL, you will get the two warnings but, because the snippet is run, you will see the interactive debugger with the error “The variable SQUALE is unbound”.
Unbound variables
“unbound” variables were not bound to anything, not even nil. Their symbol might exist, but they have no associated value.
You can create such variables like this:
(defparameter *connection*)
This defparameter
form is correct. You didn’t give any default
value: the parameter is unbound.
You can check if a variable (or a function) is bound with boundp
(or
fboundp
). The p
is for “predicate”.
You can make a variable (or function) unbound with makunbound
(or fmakunbound
).
Global variables are thread safe
Don’t be afraid of accessing and set-ing global bindings in
threads. Each thread will have its own copy of the
variable. Consequently, you can bind them to other values with let
bindings, etc. That’s good.
It’s only if you want one single source of truth that you’ll have to share the variable between threads and where the danger lies. You can use a lock (very easy), but that’s all another topic.
Addendum: defconstant
defconstant
is here to say something is a constant and is not
supposed to change, but in practice defconstant
is annoying. Use
defparameter
, and add a convention with a new style of earmuffs:
(defparameter +pi+ pi
"Just to show that pi exists but has no earmuffs. Now it does. You shouldn't change a variable with +-style earmuffs, it's a constant.")
defconstant
is annoying because, at least on SBCL, it can’t be
redefined without asking for validation through the interactive
debugger, which we may often do during development, and its default
test is eql
, so give it a string and it will always think that the
constant was redefined. Look (evaluate each line one by one in order):
(defconstant +best-lisper+ :me)
;; so far so good.
(defconstant +best-lisper+ :me)
;; so far so good: we didn't redefine anything.
(defconstant +best-lisper+ :you)
;; => the constant is being redefined, we get the interactive debugger (SBCL):
The constant +BEST-LISPER+ is being redefined (from :ME to :YOU)
[Condition of type SB-EXT:DEFCONSTANT-UNEQL]
See also:
Common Lisp Hyperspec, DEFCONSTANT [:macro]
SBCL Manual, Idiosyncrasies [:node]
Restarts:
0: [CONTINUE] Go ahead and change the value.
1: [ABORT] Keep the old value.
2: [RETRY] Retry SLIME REPL evaluation request.
3: [*ABORT] Return to SLIME's top level.
4: [ABORT] abort thread (#<THREAD tid=573581 "repl-thread" RUNNING {120633D123}>)
;; => presse 0 (zero) or click on the "Continue" restart to accept changing the value.
With constants as strings:
(defconstant +best-name+ "me")
;; so far so good, we create a new constant.
(defconstant +best-name+ "me")
;; => interactive debugger!!
The constant +BEST-NAME+ is being redefined (from "me" to "me")
…
As you will see in the equality chapter, two strings are not equal by
eql
that is a low-level equality operator (think pointers), they are
equal
(or string-equal
).
This is defconstant
documentation:
Define a global constant, saying that the value is constant and may be compiled into code. If the variable already has a value, and this is not EQL to the new value, the code is not portable (undefined behavior). The third argument is an optional documentation string for the variable.
The eql
thing is in the spec, what an implementation should do when
redefining a constant is not defined, so it may vary with your
implementation.
We invite you to look at:
- Alexandria’s define-constant, which has a
:test
keyword (but still errors out on redefinition). - Serapeum’s
defconst
cl:defparameter
;)
Guidelines and best practices
A few style guidelines:
- create all your top-level parameters at the top of a file
- define first parameters then variables
- use docstrings
- read your compiler’s warnings
- it’s better for your functions to accept arguments, rather than to rely on top-level parameters
- your functions shouldn’t mutate (modify) a top-level binding. You should create a new data structure instead, and use your function’s return value as the parameter to another function, and have data flow from one function to another.
- parameters are best for: a webserver port, a default value… and other user-facing parameters.
- variables are best for long-living and internal variables: caches, DB connections…
- you can forget about defconstant
- when in doubt, use a
defparameter
- the pattern where a function parameter is by default a global variable is typical and idiomatic:
;; from the STR library.
(defvar *whitespaces* (list #\Backspace #\Tab #\Linefeed #\Newline #\Vt #\Page
#\Return #\Space #\Rubout
;; edited for brevity
))
(defun trim-left (s &key (char-bag *whitespaces*))
"Removes all characters in `char-bag` (default: whitespaces) at the beginning of `s`."
(when s
(string-left-trim char-bag s)))
the default value can also be a function call:
;; from the Lem editor
(defun buffer-modified-p (&optional (buffer (current-buffer)))
"Return T if 'buffer' has been modified, NIL otherwise."
(/= 0 (buffer-%modified-p buffer)))
- these let bindings over global variables are idiomatic too:
(let ((*name* "other")) …)
.