Symbols
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
does foo
mean?
Or functions? What is foo
after you evaluate (defun foo ())
?
Is there a difference between (defvar FOO 5)
and the previous (lowercase)
definition?
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 describe
it!
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.
Type: SYMBOL
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, 'foo
.
Note: one important result of this is that symbols can be created, stored, and passed around (as values of other symbols).
Print Name
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:
:inherited
the symbol has been inherited from another package, via [USE-PACKAGE][clhs-use-package].:external
the symbol is present in the package, and is external. So other packages will inherit it if they use this package.:internal
the symbol is present in the package, but not inherited or external.
Value
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
Function: #<Compiled-function FOO #x30200122EBCF>
The sharp bracket representation, #<...>
, is used for unreadable (by the Lisp
reader) data.
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
or the #'
reader macro (these functions do other things if the symbol is a
macro or special operator, see the CLHS pages for more).
Plist
This symbol has no properties. See this CLL page for more details.
Class
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.
And More!
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.