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).

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.