Common Lisp symbols hide an extraordinary amount of complexity behind their simple appearances. “Symbols” are more than just variables…
What’s a symbol?
It’s a variable, obviously. See, I can do
(defvar foo 4)so “foo” must be a variable with value 4. What a silly question.
Well. Okay, how about classes?
(defclass foo () ()) is also valid – now what
Or functions? What is
foo after you evaluate
(defun foo ())?
Is there a difference between
(defvar FOO 5) and the previous (lowercase)
Let’s have a look. Here is a REPL transcript defining some foo:
CL-USER> (defvar foo 4) FOO CL-USER> (defclass foo () ()) #<STANDARD-CLASS COMMON-LISP-USER::FOO> CL-USER> (defun foo ()) FOO
Now, you can
CL-USER> (describe 'foo) FOO Type: SYMBOL Class: #<BUILT-IN-CLASS SYMBOL> Special Variable, Function, Type Specifier, Class Name INTERNAL in package: #<Package "COMMON-LISP-USER"> Print name: "FOO" Value: 4 Function: #<Compiled-function FOO #x30200122EBCF> Arglist: NIL Plist: NIL Class: #<STANDARD-CLASS FOO>
Sidenote: DESCRIBE displays information (on
\*standard-output\* by default) about the given object. You can also
use INSPECT to explore objects interactively.
Lets go over each of those things.
The thing we have inspected is an instance of the built-in class called SYMBOL.
This is already more complicated than you might have thought. A symbol is
actually an “object” with its own chunk of memory. It is not simply a
compiler/interpreter name or flag. We can access the symbol by evaluating
(quote foo), or, more commonly,
Note: one important result of this is that symbols can be created, stored, and passed around (as values of other symbols).
The symbol we inspected (when we typed
'foo) has a name of “FOO”. This is used
for lots of things, such as forming part of a printed representation of the
symbol (the printed representation might also contain a package name).
Note – it is uppercase here, but it does not have to be. It is possible to configure how the reader handles character case. But that’s not important now. More info [here][https://www.cs.cmu.edu/afs/cs/project/ai-repository/ai/html/cltl/clm/node192.html].
Internal in Package
INTERNAL in package: #<Package "COMMON-LISP-USER">
The symbol being inspected is internal to the COMMON-LISP-USER package (abbreviated to CL-USER at the REPL).
This is important. It could be interpreted to mean that symbols have “home” packages. There are quotation marks around that because it is not quite precise, but is a useful way to think about it. Essentially, symbols are objects, and, to access them, you either need a reference to them in memory, or you need to know their string name and “home package”.
A “plain” symbol can be interned with the INTERN function, which
either gets an existing symbol, or creates a new one and enters it into a
package. For example,
(intern "BAR") checks whether the symbol “BAR” is
accessible in the current package, returning it if so. If not, a new symbol
named “BAR” is created, and entered into the current package. It will not be
bound to any value, it will not have a function value, and it will not name a
class. However it is still a symbol!
(intern "BAR") (symbolp 'bar) ; => T
Another useful function to know about is FIND-SYMBOL. This will tell you whether the symbol is accessible in the given package, and the “status” of the symbol.
If a symbol is accessible in a package, the status will be one of the following keywords:
:inheritedthe symbol has been inherited from another package, via [USE-PACKAGE][clhs-use-package].
:externalthe symbol is present in the package, and is external. So other packages will inherit it if they use this package.
:internalthe symbol is present in the package, but not inherited or external.
The symbol can be used to access a variable which currently bound to the integer
value 4. If we ever try to use this symbol in a situation where variable access
is required, 4 will be used. For example,
(+ foo 5) will evaluate to 9.
With our previously interned symbol
BAR, we could assign it a value
using SET or SETQ:
(set 'bar 101) (setq bar 102)
(setq bla 10) is roughly equivalent to
(set 'bla 10), and can be thought of
as “set quoted”. It sets the value of the symbol
bla, rather than the value of
the symbol stored in
bla. This is an oversimplification, as
SET won’t work
on lexically scoped variables, while
SETQ will. Have a look at their CLHS
pages for more precise detail.
Also note – you can access the value of a symbol using SYMBOL-VALUE.
Function: #<Compiled-function FOO #x30200122EBCF>
The sharp bracket representation,
#<...>, is used for unreadable (by the Lisp
The symbol can also be used to access a function (which happens to be
compiled, and lives at that memory address). Whether a function or variable is
accessed depends solely on the context the symbol is used in. For example,
(foo) will call the function named by the symbol
foo. If a symbol has a
function bound to it (which you can check using FBOUNDP), you
can retrieve the function using either SYMBOL-FUNCTION
#' reader macro (these functions do other things if the symbol is a
macro or special operator, see the CLHS pages for more).
This symbol has no properties. See this CLL page for more details.
This is not shown in the
describe output, but the symbol also names a class.
An instance of this class can be created with
(make-instance 'foo). This is
shown if you use
M-x slime-inspect in Emacs to inspect
'foo, or if you use
the [find-class] function.
This is only a quick overview, intended to show you a few ways in which symbols are not just “variables”. There are many more interpretations for symbols than the ones covered here.