Why this actually happens
The other answer is slightly incomplete because it missed the detail that you are using SBCL, which has the following caveat:
SBCL is quite strict about ANSI’s definition of defconstant. ANSI says that doing defconstant of the same symbol more than once is undefined unless the new value is eql to the old value. Conforming to this specification is a nuisance when the “constant” value is only constant under some weaker test like string= or equal.
It’s especially annoying because, in SBCL, defconstant takes effect not only at load time but also at compile time, so that just compiling and loading reasonable code like (defconstant +foobyte+ '(1 4))
runs into this undefined behavior. Many implementations of Common Lisp try to help the programmer around this annoyance by silently accepting the undefined code and trying to do what the programmer probably meant.
This is from the manual page https://www.sbcl.org/manual/#Defining-Constants, reformatted slightly and emphasis added.
Credit goes to to the user Flux in this comment for linking to the relevant page. Thank you also to the users of #commonlisp
on Libera.chat for helping me to clarify my understanding.
Possible solutions
Invoke defconstant
only at compilation time
I have worked around this problem using eval-when
, e.g.:
(eval-when (:compile-toplevel)
(defconstant no-bindings '((t . t))
"Indicates pat-match success, with no variables."))
This does not help with subsequent recompilations, but it does prevent the surprising error described in the original question.
Ignore subsequent defconstant
invocations
The SBCL manual suggests another approach, using a macro to check if the symbol is bound before invoking defconstant
, re-assigning the constant to itself if it is bound:
(defmacro define-constant (name value &optional doc)
`(defconstant ,name (if (boundp ',name)
(symbol-value ',name)
,value)
,@(when doc (list doc))))
With this technique, subsequent invocations of defconstant
are effectively ignored. If you re-define a constant with this macro, it will be silently ignored, even if the value is totally different.
Exercise greater control over the value comparison
As an alternative, and if you don't mind using an external dependency on the Alexandria library (which is nearly ubiquitous anyway), you can use alexandria:define-constant
(source) for this purpose.
Instead of ignoring subsequent defconstant
invocations as in the SBCL recipe, this version lets you control the function used for comparison. For instance, you might want to use equal
instead of the default eql
.
Using equal
might be a good choice in general, as per the Hyperspec entry for EQUAL
:
Returns true if x and y are structurally similar (isomorphic) objects. Objects are treated as follows by equal.
IRC user jcowan
interprets "isomorphic" to mean
(roughly speaking) equality at the code level
which perhaps is what most people would want when binding literal values to a constant.
(defconstant B "B")
twice in the sbcl repl. – Handbill