Chapter 3 The CL Language
If you are new
to the CL language, we recommend that you supplement this chapter with other resources.
See the Bibliography in Appendix A for some suggestions. The Bibliography also
lists two interactive online CL tutorials from Tulane University and Texas
A&M, which you may wish to visit.
In the
meantime, this chapter will provide a condensed overview of the language. Keep
in perspective, however, that this booklet is intended as a summary, and will
not delve into some of the more subtle and powerful techniques possible with
Common Lisp.
The first thing
you should observe about CL (and most languages in the Lisp family) is that it
uses a generalized prefix notation.
One of the most
frequent actions in a CL program, or at the toplevel read-eval-print loop, is to
call a Function. This is most often done by writing an expression which names the
Function, followed by its arguments. Here is an example:
(+ 2 2)
This expression
consists of the Function named by the Symbol "+," followed by the
arguments 2 and another 2. As you may have guessed, when this expression is evaluated it will return
the value 4.
Try it: Try typing this
expression at your command prompt, and see the return-value being printed on
the console.
What is it that
is actually happening here? When CL is asked to evaluate an expression (as in the
toplevel read-eval-print loop), it evaluates the expression according to the
following rules:
USER(8): 99
99
USER(14): (expt 2 5)
32
Try it: Try typing the following functional expressions at your command prompt, and convince yourself that the printed return-values make sense:
(+ 2 2)
(+ 2)
2
(+)
(+ (+ 2 2) (+ 3 3))
(+ (+ 2 2))
(user-id)
(user-homedir-pathname)
(get-universal-time) ;; returns number of seconds since January 1, 1900
(search "Dr." "I am Dr. Strangelove")
"I am Dr. Strangelove"
(subseq (search "Dr." "I am Dr. Strangelove")
"I am Dr. Strangelove")
Note that the arguments to a Function can themselves be any kind of the above expressions. They are evaluated in order, from left to right, and finally they are passed to the Function for it to evaluate. This kind of nesting can be arbitrarily deep. Do not be concerned about getting lost in an ocean of parentheses - most serious text editors can handle deeply nested parentheses with ease, and moreover will automatically indent your expressions so that you, as the programmer, never have to concern yourself with matching parentheses.
One of the
nicest things about Lisp is the simplicity and consistency of its syntax. What
we have covered so far pertaining to the evaluation of arguments to Functions
is just about everything you need to know about the syntax. The
rules for Macros are very similar, with the primary difference being that all
the arguments of a Macro are not necessarily evaluated at the time the Macro is
called - they can be transformed into something else first.
This simple
consistent syntax is a welcome relief from other widely used languages such as
C, C++, Java, Perl, and Python. These languages claim to use a "more
natural" "infix" syntax, but in reality their syntax is a confused
mixture of prefix, infix, and postfix. They use infix only for the simplest
arithmetic operations such as
2 + 2
and
3 * 13
and use postfix in certain cases such as
i++
and
char*
But mostly they actually use a prefix syntax much the same as Lisp, as in:
split(@array, ":");
So, if you have been programming in these other languages, you have been using prefix notation all along, but may not have noticed it. If you look at it this way, the prefix notation of Lisp will seem much less alien and, in fact, downright natural to you.
Sometimes you want to specify an expression at the toplevel read-eval-print loop, or inside a program, but you do not want CL to evaluate this expression. You want just the literal expression. CL provides the special operator quote for this purpose. For example,
(quote a)
will return the
literal Symbol A. Note that, by default, the CL reader will convert all Symbol names to
uppercase at the time the Symbol is read into the system. Note also that
Symbols, if evaluated "as is," (i.e. without the quote), will by
default behave as variables, and CL will attempt to
retrieve an associated value. More on this later.
Here is another
example of using quote to return a literal expression:
(quote (+ 1 2))
Unlike
evaluating a List expression "as is," this will return the literal List (+ 1 2). This List may
now be manipulated as a normal List data structure.
Common Lisp
defines the abbreviation ' as a shorthand way to "wrap" an expression in
a call to quote. So the previous examples could equivalently be written as:
'a
'(+ 1 2)
Common Lisp
natively supports many data types common to other languages, such as Numbers, Strings,
and Arrays. Native to CL is also a set of types which you may not have come
across in other languages, such as Lists, Symbols, and Hash tables. In this
overview we will touch on Numbers, Strings, Symbols, and Lists. Later in the
book we will provide more detail on some CL-specific data types.
Regarding data
types, CL follows a paradigm called dynamic typing. Basically this
means that values have type, but variables do not necessarily have type, and typically
variables are not "predeclared" to be of a particular type.
Numbers
Numbers in CL form a
hierarchy of types, which includes Integers, Ratios, Floating Point, and Complex Numbers. For
many purposes you only need to think of a value as a "Number," without getting
any more specific than that. Most arithmetic operations, such as +, -, *, /, etc, will automaticaly
do any necessary type coercion on their arguments and will return a Number of
the appropriate type.
CL supports a
full range of oating-point decimal Numbers, as well as true Ratios, which means that
1/3
is
a true one-third, not 0.333333333 rounded ofi at some
arbitrary precision.
As we have
seen, Numbers in CL are a native data type which simply evaluate to themselves when
entered at the toplevel or included in an expression.
Strings
Strings are actually a
specialized kind of Array, namely a one-dimensional array (vector)
made up of characters. These characters can be letters, Numbers, or
punctuation, and in some cases can include characters from international
character sets (e.g. Unicode) such as Chinese Hanzi or Japanese Kanji. The
String delimiter in CL is the double-quote character (").
As we have
seen, Strings in CL are a native data type which simply evaluate to themselves
when included in an expression.
Symbols
Symbols are such an
important data structure in CL that people sometimes refer to CL as a "symbolic
computing" language. Symbols are a type of CL object which provides your
program with a built-in mechanism to store and retrieve Values and Functions,
as well as being useful in their own right. A Symbol is
most often known by its name (actually a String), but in fact there is much
more to a Symbol than its name. In addition to the name, Symbols also contain a
Function
slot,
a Value slot, and an open-ended Property-list slot in which you can
store an arbitrary number of named properties.
For a named
Function such as +, the Function-slot of the Symbol + contains the actual Function itself. The
Value slot of a Symbol can contain any value, allowing the Symbol to act as a
global variable, or Parameter. And the Property-list, or Plist slot, can
contain an arbitrary amount of information.
This separation
of the Symbol data structure into Function, Value, and Plist slots is one
obvious distinction between Common Lisp and most other Lisp dialects. Most
other dialects allow only one (1) "thing" to be stored in the Symbol data
structure, other than its name (e.g. either a Function or a Value, but not both
at the same time). Because Common Lisp does not impose this restriction, it is
not necessary to contrive names, for example for your variables, to avoid
conicting with existing "reserved words" in the system. For example, "list" is the
name of a built-in Function in CL. But you may freely use "list" as a
variable as well. There is no need to contrive arbitrary abbreviations such as
"lst."
How Symbols are
evaluated depends on where they occur in an expression. As we have seen, if a
Symbol appears first in a List expression, as with the + in (+ 2 2), the Symbol is evaluated for
its Function slot. If the first element of an expression is a Symbol which
indeed does contain a Function in its Function slot, then any Symbol which
appears anywhere else (i.e. in the rest) of the expression is taken as a variable, and it is evaluated for its
global or local value, depending on its Scope. More on
Variables and Scope later.
As noted in
Section 3.1.3, if you want a literal Symbol itself, one way to achieve this is
to "quote" the Symbol name:
'a
Another way is for the Symbol to appear within a quoted List expression:
'(a b c)
'(a (b c) d)
Note that the quote (') applies across everything in the List expression, including any sub-expressions.
Lists
Lisp takes its
name from its strong support for the List data structure. The List concept is
important to CL for more than this reason alone - Lists are important because all CL
programs themselves are Lists! Having the List as a native data structure, as well
as the form of all programs, means that it is especially straightforward for CL
programs to compute and generate other CL programs. Likewise, CL
programs can read and manipulate other CL programs in a natural manner. This cannot be
said of most other languages, and is one of the primary distinguishing
characteristics of CL.
Textually, a
List is defined as zero or more elements surrounded by parentheses. The
elements can be objects of any valid CL data types, such as Numbers, Strings,
Symbols, Lists, or other kinds of Objects. As we have seen, you must quote a
literal List to evaluate it or CL will assume you are calling a Function.
Now look at the
following List:
(defun hello () (write-string "Hello, World!"))
This List also
happens to be a valid CL program (Function definition, in this case). Don't
worry about analyzing the Function right now, but do take a few moments to
convince yourself that it meets the requirements for a List. What are the types
of the elements in this List?
In addition to
using the quote (') to produce a literal List, another way to produce a List is to call the
Function list. The Function list takes any number of arguments, and returns a
List made up from the result of evaluating each argument (as with all
Functions, the arguments to the list Function get evaluated,
from left to right, before being passed into the Function). For example,
(list 'a 'b (+ 2 2))
will return the List (A B 4). The two quoted Symbols evaluate to Symbols, and the Function call (+ 2 2) evaluates to the Number 4.
Functions form
the basic building block of CL. Here we will give a brief overview of how to
define a Function; later we will go into more detail on what a Function
actually is.
A common way to
define named Functions in CL is with the macro defun, which stands
for DEFinition of a FUNction. Defun takes as its arguments a Symbol, an Argument
List, and a Body:
(defun
my-first-lisp-function ()
(list 'hello
'world))
Because defun is a Macro, rather than a
Function, it does not follow the hard-and-fast rule that all its arguments are
evaluated as expressions - specifically, the Symbol which names the Function does
not have to be quoted, nor does the Argument List. These are taken as a literal
Symbol and a literal List, respectively.
Once the
Function has been defined with defun, you can call it just as you would call any
other Function, by wrapping it in parentheses together with its arguments:
USER(56):
(my-first-lisp-fn)
(HELLO WORLD)
USER(57):
(defun square(x)
(* x x))
SQUARE
USER(58):
(square 4)
16
Declaring the types of the arguments to a Function is not required.
Global Variables
Global
variables in CL are usually also known as special variables. They
can be established by calling defparameter or defvar from the read-eval-print
loop,
or from within a file that you compile and load into CL:
(defparameter
*todays-temp* 90)
(defvar
*humidity* 70)
The surrounding
asterisks on these Symbol names are part of the Symbol names themselves, and have
no significance to CL. This is simply a long-standing naming convention for
global variables (i.e. parameters), to make them easy to pick out with the
human eye.
Defvar and defparameter differ in one
important way: if defvar is evaluated with a Symbol which already has a global value, it will not overwrite it
with a new value. Defparameter, on the other hand, will overwrite it with a new value:
USER(4):
*todays-temp*
90
USER(5):
*todays-humidity*
70
USER(6):
(defparameter *todays-temp* 100)
*TODAYS-TEMP*
USER(7):
(defvar *todays-humidity* 50)
*TODAYS-HUMIDITY*
USER(8):
*todays-temp*
100
USER(9):
*todays-humidity*
70
*todays-humidity*
did
not change because we used defvar, and it already had a previous value.
The value for
any Parameter can always be changed from the toplevel with setq:
USER(11):
(setq *todays-humidity* 30)
30
USER(12):
*todays-humidity*
30
Although setq will work with
new variables, stylistically it should only be used with alreadyestablished variables.
During
application development, it often makes sense to use defvar rather than defparameter for variables
whose values might change during the running and testing of the program. This
way, you will not unintentionally reset the value of a parameter if you compile
and load the source file where it is defined.
Local Variables
New local
variables can be introduced with the Macro let:
USER(13): (let
((a 20)
(b 30))
(+ a b))
50
As seen in the above example, let takes an assignment section and a body. Let is a Macro, rather than a Function, so it does not follow the hard-and-fast rule that all its arguments are evaluated. Specifically, the assignment section
((a 20)
(b 30))
is not
evaluated (actually, the Macro has the effect of transforming this into
something else before the CL evaluator even sees it). Because this assignment
section is not evaluated by the let Macro, it does not have to
be quoted, like a List which is an argument to a Function would have to be. As you
can see, the assignment section is a List of Lists, where each internal List is
a pair whose first is a Symbol, and whose second is a value (which will be evaluated).
These Symbols (a and b) are the local variables, and they are assigned to the respective values.
The Body consists of any
number of expressions which come after the assignment section and before the
closing parenthesis of the let statement. The expressions in this Body are
evaluated normally, and of course any expression can refer to the value of any
of the local variables simply by referring directly to its Symbol. If the Body
consists of more than one expression, the final return-value of the Body is the
return-value of its last expression.
New Local
variables in CL are said to have lexical scope, which means that
they are only accessible in the code which is textually contained within the
body of the let. The term lexical is derived from the fact that the behavior of these variables can be
determined simply by reading the text of the source code, and is not affected
by what happens during the program's execution.
Dynamic scope happens
when you basically mix the concept of global parameters and local let variables. That
is, if you use the name of a previously established parameter inside the
assignment section of a let, like this:
(let
((*todays-humidity* 50))
(do-something))
the parameter
will have the specified value not only textually within the the Body of the let, but also in any
Functions which may get called from within the body of the let. The global
value of the parameter in any other contexts will not be affected. Because this
scoping behavior is determined by the runtime "dynamic" execution of the
program, we refer to it as Dynamic scope.
Dynamic scope
is often used for changing the value of a particular global parameter only
within a particular tree of Function calls. Using Dynamic scope, you can
accomplish this without affecting other places in the program which may be
referring to the parameter. Also, you do not have to remember to have your code
"reset" the parameter back to a default global value, since it will automatically
"bounce" back to its normal global value.
Dynamic Scoping
capability is especially useful in a multithreaded CL, i.e., a CL process
which can have many (virtually) simultaneous threads of execution. A parameter
can take on a dynamically scoped value in one thread, without affecting the
value of the parameter in any of the other concurrently running threads.
In this section we will present some of the fundamental native CL operators for manipulating Lists as data structures. These include operators for doing things such as:
1. finding the length of a List
2. accessing particular members of a List
3. appending multiple Lists together to make a new List
4. extracting elements from a List to make a new List
Common Lisp defines the accessor Functions first through tenth as a means of accessing the first ten elements in a List:
USER(5):
(first '(a b c))
A
USER(6):
(second '(a b c))
B
USER(7):
(third '(a b c))
C
For accessing elements in an arbitrary position in the List, you can use the Function nth, which takes an integer and a List as its two arguments:
USER(8): (nth
0 '(a b c))
A
USER(9): (nth
1 '(a b c))
B
USER(10): (nth
2 '(a b c))
C
USER(11): (nth
12 '(a b c d e f g h i j k l m n o p))
M
Note that nth starts its indexing at zero (0), so (nth 0 ...) is equivalent to (first ...), and (nth 1 ...) is equivalent to (second ...), etc.
A very common operation in CL is to perform some operation on the first of a List, then perform the same operation on the rest of the List, repeating the procedure until the end of the List, i.e. the Empty List, is reached. The Function rest is very helpful in such cases:
USER(59):
(rest '(a b c))
(B C)
As you can see from this example, rest returns a List consisting of all but the first of its argument.
The Symbol NIL is defined by Common Lisp to be equivalent to the Empty List, (). NIL also has the interesting property that its Value is itself, which means that it will always evaluate to itself, whether or not it is quoted. So NIL, 'NIL, and () all evaluate to the Empty List, whose default printed representation is NIL:
USER(14): nil
NIL
USER(15): 'nil
NIL
USER(16): ()
NIL
The Function null can be used to test whether or not something is the Empty List:
USER(17):
(null '(a b c))
NIL
USER(18):
(null nil)
T
USER(19):
(null ())
T
As you may have deduced from the above examples, the Symbol T is CL's default representation for True. As with NIL, T evaluates to itself.
To test whether or not a particular object is a List, CL provides the Function listp. Like many other Functions which end with the letter "p," this Function takes a single argument and checks whether it meets certain criteria (in this case, whether it qualifies as a List). These predicate Functions ending in the letter "p" will always return either T or NIL:
USER(20):
(listp '(pontiac cadillac chevrolet))
T
USER(21):
(listp 99)
NIL
USER(22):
(listp nil)
T
Note that (listp nil) returns T, since NIL is indeed a List (albeit the empty one).
Before
continuing with a number of other basic List Functions, we will cover the macro
if, which allows
simple conditionals. If takes three arguments, a test-form, a then-form, and an else-form.
When an if form is
evaluated, it first evaluates its Test-form. If the form returns non-NIL, it will evaluate
the Then-form, else it will evaluate the Else-form. The nesting of multiple if expressions is possible,
but not advised; later we will cover other constructs which are more
appropriate for such cases.
Here are some
simple examples using the if macro:
USER(2): (if
(> 3 4)
"no"
"yes")
"yes"
USER(3): (if
(listp '("Chicago" "Detroit" "Toronto"))
"it
is a list"
"it ain't a list")
"it is a
list"
USER(4): (if (listp
99)
"it is a
list"
"it ain't a list")
"it ain't
a list"
Normally you can use the Function length to get the number of elements in a List as an Integer:
USER(5):
(length '(gm ford chrysler volkswagen))
4
USER(6):
(length nil)
0
The length Function, as with most of Common Lisp, can itself be implemented in CL. Here is a simplified version of an our-length Function which illustrates how length might be implemented:
(defun
our-length (list)
(if (null
list)
0
(+ (our-length
(rest list)) 1)))
Note that the
Function uses the Symbol list to name its argument, which is perfectly
valid as we discussed in section 3.1.4.
In English,
this Function says basically: "If the List is empty, its length is zero.
Otherwise its length is one greater than the length of its rest." As with
many Functions which operate on Lists, this recursive definition is a
natural way to express the length operation.
The member Function will
help you to determine whether a particular item is an element of a particular
List. Like many similar Functions, member uses eql to test for
equality, which is one of the most basic equality Functions in CL (see Section
3.8.4 for a discussion of equality in CL). Eql basically means
the two objects must be the same Symbol, Integer, or actual Object (i.e. the
same address in memory).
Member takes
two arguments: an Item and a List. If the Item is not in the List, it returns NIL.
Otherwise, it
returns the rest of the List, starting from the found element:
USER(7):
(member 'dallas '(boston san-francisco portland))
NIL
USER(8):
(member 'san-francisco '(boston san-francisco portland))
(SAN-FRANCISCO
PORTLAND)
As with length, we could define member using a Function definition which is very close to the English description of what the Function is supposed to do:
(defun our-member (elem list)
(if (null
list)
nil
(if (eql elem
(first list))
list
(our-member
elem (rest list)))))
In English, you
might read this Function to say "If the List is empty, return NIL. Otherwise, if
the desired item is the same as the first of the List, return the
entire List. Otherwise, do the same thing on the rest of the
List."
Note that, for
the purposes of any kind of logical operators, returning any non-NIL value is "as good
as" returning T. So the possible return-values of the member Function are as
good as returning T or NIL as far as any logical operators are concerned.
Subseq is a common Function to use for returning a portion of a List (or actually any type of Sequence). Subseq takes at least two arguments, a List and an Integer indicating the position to start from. It also takes an optional third argument, an Integer indicating the position to stop. Note that the position indicated by this third argument is not included in the returned sub-List:
USER(9):
(subseq '(a b c d) 1 3)
(B C)
USER(10):
(subseq '(a b c d) 1 2)
(B)
USER(11):
(subseq '(a b c d) 1)
(B C D)
Note also that the optional third argument in effect defaults to the length of the List.
The Function append takes any number of Lists, and returns a new List which results from appending them together. Like many CL Functions, append does not side-effect, that is, it simply returns a new List as a return-value, but does not modify its arguments in any way:
USER(6): (setq
my-slides '(introduction welcome lists Functions))
(INTRODUCTION
WELCOME LISTS FUNCTIONS)
USER(7):
(append my-slides '(numbers))
(INTRODUCTION
WELCOME LISTS FUNCTIONS NUMBERS)
USER(8):
my-slides
(INTRODUCTION
WELCOME LISTS FUNCTIONS)
USER(9): (setq
my-slides (append my-slides '(numbers)))
(INTRODUCTION
WELCOME LISTS FUNCTIONS NUMBERS)
USER(10):
my-slides
(INTRODUCTION
WELCOME LISTS FUNCTIONS NUMBERS)
Note that the simple call to append does not affect the variable my-slides. If we wish to modify the value of this variable, however, one way to do this is by using setq in combination with the call to append. Note also the use of setq directly at the toplevel with a new variable name. For testing and "hacking" at the Command Prompt, this is acceptable, but in a finished program all such global variables should really be formally declared with defparameter or defvar.
To add a single element to the front of a List, you can use the Function cons:
USER(13):
(cons 'a '(b c d))
(A B C D)
Cons is actually the
low-level primitive Function upon which many of the other List-constructing Functions
are built. As you may have guessed, "cons" stands for
"CONStruct," as in "Construct a List." When you read or hear people
talking about a CL program doing a lot of "consing," they are referring to the program's behavior of
building a lot of List structures, many of which are transitory and will need
to be "freed up" by the automatic memory management subsystem, or "garbage
collector."
As with append, cons is non-destructive, meaning it
does no side-effecting (modification) to its arguments.
The Function remove takes two arguments, any item and a List, and returns a new List with all occurences of the item taken out of it:
USER(15):
(setq data '(1 2 3 1000 4))
(1 2 3 1000 4)
USER(16):
(remove 1000 data)
(1 2 3 4)
USER(17): data
(1 2 3 1000 4)
USER(18):
(setq data (remove 1000 data))
(1 2 3 4)
USER(19): data
(1 2 3 4)
Like append and cons, remove is non-destructive and so does not modify its arguments. As before, one way to achieve modification with variables is to use setq.
The Function sort will sort any Sequence (including, of course, a List), based on comparing the elements of the Sequence using any applicable comparison Function, or predicate Function. Because of efficiency reasons, the designers of CL made the decision to allow sort to modify ("recycle" the memory in) its Sequence argument - so you must always "catch" the result of the sorting by doing something explicitly with the return-value:
USER(20):
(setq data '(1 3 5 7 2 4 6))
(1 3 5 7 2 4
6)
USER(21):
(setq data (sort data #'<))
(1 2 3 4 5 6
7)
USER(22):
(setq data (sort data #'>))
(7 6 5 4 3 2
1)
Notice that the
comparison Function must be a Function which can take two arguments - any representative
two elements in the given sequence - and compare them to give a T or NIL result.
The "#'" notation
is a shorthand way of retrieving the actual Function object associated with a Symbol
or expression (in this case, the < or > Symbol). We
will cover more on this in Section 3.4. In the above examples, we simply reset
the value of the variable data to the result of the sort, which is a common
thing to do. If one wanted to retain the original pre-sorted sequence, a
"safe" version of sort could be defined as follows:
(defun
safe-sort (list predicate)
(let
((new-list (copy-list list)))
(sort new-list
predicate)))
The Functions union, intersection, and set-difference take two Lists and compute the corresponding set operation. Because mathematical sets have no notion of ordering, the order of the results returned by these Functions is purely arbitrary, so you should never depend on the results of these set operations being in any particular order:
USER(23):
(union '(1 2 3) '(2 3 4))
(1 2 3 4)
USER(25):
(intersection '(1 2 3) '(2 3 4))
(3 2)
USER(26):
(set-difference '(1 2 3 4) '(2 3))
(4 1)
If you have one List, and desire another List of the same length, there is a good chance that you can use one of the mapping Functions. Mapcar is the most common of such Functions. Mapcar takes a Function and one or more Lists, and maps the Function across each element of the List, producing a new resulting List. The term car comes from the original way in Lisp of referring to the first of a List ("Contents of Address Register"). Therefore mapcar takes its Function and applies it to the first of each successive rest of the List:
USER(29):
(defun twice (num)
(* num 2))
TWICE
USER(30):
(mapcar #'twice '(1 2 3 4))
(2 4 6 8)
"Lambda" (unnamed) Functions are used very frequently with mapcar and similar mapping Functions. More on this in Section 3.4.3.
Property Lists ("Plists") provide a simple yet powerful way to handle keyword-value pairs. A Plist is simply a List, with an even number of elements, where each pair of elements represents a named value. Here is an example of a Plist:
(:michigan
"Lansing" :illinois "Springfield"
:pennsylvania
"Harrisburg")
In this Plist,
the keys are Keyword Symbols, and the values are Strings. The Keys in a
Plist are very often Keyword Symbols. Keyword Symbols are Symbols whose names
are preceded by a colon (:), and which are generally used just for matching
the Symbol itself (i.e. typically they are not used for their Symbol-value or
Symbol-function).
For accessing
members of a Plist, CL provides the Function getf, which takes a
Plist and a Key:
USER(34):
(getf '(:michigan "Lansing"
:illinois
"Springfield"
:pennsylvania
"Harrisburg")
:illinois)
"Springfield"
"Control of Execution" in CL boils down to "Control of Evaluation." Using some standard and common Macros, you control which forms get evaluated, among certain choices.
Perhaps the
most basic Macro for imposing control is the if Macro, which we
have already introduced briey in Section 3.2.5. The If Macro takes
exactly three arguments, each of which is an expression which may be evaluated.
The first is a test-form, the second is a then-form, and the third is an else-form (which may be
left out for a default of NIL). When CL evaluates the if form, it will
first evaluate the test-form. Depending on whether the return-value of
the Test-form is non-NIL or NIL, either the Then-form or the Else-form will
be evaluated.
If you want to
group multiple expressions together to occur as part of the Then-form or the Else-form,
you must wrap them in an enclosing block. Progn is commonly
used for this purpose.
Progn will accept any
number of forms as arguments, and will evaluate each of them in turn, finally returning
the return-value of the last of them (see Figure 3.1).
In some ways, when is similar to if, but you should use when in cases where you know that the Else-clause is simply NIL. This turns out to be a common situation. Another advantage of using when in these cases is that there is no need for progn to group multiple forms - when simply takes a Test-form then any number of "Then-forms:"
USER(36):
(when (eql database-connection :active)
(read-record-from-database)
(chew-on-data-from-database)
(calculate-final-result))
999
USER(37):
(when (> 3 4)
(princ "I don't think so..."))
NIL
The Logical Operators and and or evaluate one or more expressions given as arguments, depending on the return-values of the expressions. As we covered in Section 3.2.3, T is CL's default representation for "True," NIL (equivalent to the Empty List) is CL's default representation for "False," and any non-NIL value is "as good as" T as far as "Truth" is concerned:
USER(38): (and
(listp '(chicago detroit boston new-york))
(listp 3.1415))
NIL
USER(39): (or
(listp '(chicago detroit boston new-york))
(listp 3.1415))
T
In fact what is
happening here is that and evaluates its arguments (expressions) one at
a time, from left to right, returning NIL as soon as it finds one
which returns NIL - otherwise it will return the value of its last expression. Or evaluates its
arguments until it finds one which returns non-NIL, and returning
that non-NIL return-value if it finds one. Otherwise it will end up returning NIL.
Not takes a single
expression as an argument and negates it - that is, if the expression returns NIL, not will return T, and if the
argument returns non-NIL, not will return NIL. Logically, not behaves
identically with the Function null - but semantically, null carries the
meaning of "testing for an Empty List," while not carries the
meaning of Logical Negation:
USER(45): (not
NIL)
T
USER(46): (not
(listp 3.1415))
T
Because the use of "nested" if statements is not appropriate, CL provides the Macro cond to accommodate this. Use cond in situations when in other languages you might use a repeated "If...then...else if...else if...else if..." Cond takes as arguments any number of test-expression forms. Each of these forms consists of a List whose first is an expression to be evaluated, and whose rest is any number of expressions which will be evaluated if the first form evaluates to non-NIL. As soon as a non-NIL form is found, its corresponding expressions are evaluated and the entire cond is finished (i.e. there is no need for an explicit "break" statement or anything of that nature):
USER(49): (let
((n 4))
(cond ((> n 10) "It's a lot")
((= n 10)
"It's kind of a lot")
((< n 10)
"It's not a lot")))
"It's not
a lot"
Because T as an expression simply evaluates to itself, a convenient way to specify a "default" or "catch-all" clause in a cond is to use T as the final conditional expression:
USER(50): (let
((n 4))
(cond ((> n 10) "It's a lot")
((= n 10)
"It's kind of a lot")
(t "It's not a lot")))
"It's not
a lot"
If you want to make a decision based on comparing a variable against a known set of constant values, case is often more concise than cond:
USER(51): (let
((color :red))
(case color
(:blue
"Blue is okay")
(:red
"Red is actually her favorite color")
(:green
"Are you nuts?")))
"Red is
actually her favorite color"
Note that case uses eql to do its
matching, so it will only work with constants which are Symbols (including
Keyword Symbols), Integers, etc. It will not work with Strings.
CL also
provides the macros ecase and ccase, which work just like case but provide
some automatic error-handling for you in cases when the given value does not
match any of the keys.
The Symbol otherwise has special meaning to the
case
Macro
- it indicates a "default," or "catch-all" case:
USER(52): (let
((color :orange))
(case color
(:blue
"Blue is okay")
(:red
"Red is actually her favorite color")
(:green
"Are you nuts?")
(otherwise
"We have never heard of that!")))
"We have
never heard of that!"
In addition to its innate abilities to do recursion and mapping, CL provides several operators for doing traditional iteration ("looping"). These include macros such as do, dolist, dotimes, and the "everything-including-the-kitchen-sink" iterating macro, loop. Dolist and dotimes are two of the most commonly used ones, so we will cover them here:
USER(53): (let
((result 0))
(dolist (elem
'(4 5 6 7) result)
(setq result
(+ result elem))))
22
As seen above, dolist takes an initialization-list
and
a body. The Initialization-list consists of an iterator-variable, a List-form, and an
optional return-value-form (which defaults to NIL). The Body will
be evaluated once with the Iterator-variable set to each element in the List
which is returned by the List-form. When the List has been exhausted, the
return-value-form will be evaluated and this value will be returned from the
entire dolist. In other words, the number of iterations of a dolist is driven by
the length of the List returned by its List-form.
Dotimes is similar in
many ways to dolist, but instead of a List-form, it takes an expression which should evaluate
to an Integer. The Body is evaluated once with the Iterator-variable set to each
integer starting from zero (0) up to one less than the value of the
Integer-form:
USER(54): (let
((result 1))
(dotimes (n 10
result)
(setq result
(+ result n))))
46
In situations when you want the program to exit early from an iteration, you can usually accomplish this by calling return explicitly.
When working with CL, it is important to understand that a Function is actually a kind of Object, separate from any Symbol or Symbol-name with which it might be associated. You can access the actual Function-object in a Symbol's Function-slot by calling the Function symbol-function on a Symbol, like this:
(symbol-function
'+)
#<Function
+>
or this:
(symbol-function
'list)
#<Function
LIST>
As you can see,
the printed representation of a named Function-object contains the Function's Symbol-name
enclosed in pointy brackets, preceded by a pound sign.
You can
explicitly call a Function-object with the Function funcall :
USER(10):
(funcall (symbol-function '+) 1 2)
3
USER(11):
(setq my-function (symbol-function '+))
#<Function
+>
USER(12):
(funcall my-function 1 2)
3
A shorthand way of writing symbol-function is to write #' before the name of a Function:
USER(10):
(funcall #'+ 1 2)
3
USER(11):
(setq my-function #'+)
#<Function
+>
USER(12):
(funcall my-function 1 2)
3
"Functional Arguments" are arguments to Functions which themselves are Function-objects. We have seen this already with mapcar, sort, and funcall:
USER(13):
(defun add-3 (num)
(+ num 3))
ADD-3
USER(14):
(symbol-function 'add-3)
#<Interpreted
Function ADD-3>
USER(15):
(mapcar #'add-3 '(2 3 4))
(5 6 7)
USER(17):
(funcall #'add-3 2)
5
The idea of passing Functions around to other Functions is sometimes referred to as higher-order functions. CL provides natural support for this concept.
Sometimes a Function will be used so seldom that it is hardly worth going to the extent of creating Named Function with defun. For these cases, CL provides the concept of the lambda, or "anonymous" (unnamed) Function. To use a Lambda Function, you place the entire Function definition in the position where you normally would put just the Symbol which names the Function, identified with the special Symbol lambda:
USER(19):
(mapcar #'(lambda(num) (+ num 3))
'(2 3 4))
(5 6 7)
USER(20):
(sort '((4 "Buffy") (2 "Keiko") (1 "Judy") (3
"Aruna"))
#'(lambda(x y)
(< (first
x) (first y))))
((1
"Judy") (2 "Keiko") (3 "Aruna") (4
"Buffy"))
Optional arguments to a Function must be specified after any required arguments, and are identified with the Symbol &optional in the Argument List. Each optional argument can be either a Symbol or a List containing a Symbol and an expression returning a default value. In the case of a Symbol, the default value is taken to be NIL:
USER(23):
(defun greeting-list (&optional (username "Jake"))
(list 'hello
'there username))
GREETING-LIST
USER(24):
(greeting-list)
(HELLO THERE
"Jake")
USER(25):
(greeting-list "Joe")
(HELLO THERE
"Joe")
Keyword arguments to a Function are basically named optional arguments. They are defined much the same as optional arguments:
(defun
greeting-list (&key (username "Jake")
(greeting
"how are you?"))
(list 'hello
'there username greeting))
But when you call a Function with keyword arguments, you "splice in" what amounts to a Plist containing keyword Symbols for the names of the arguments along with their values:
USER(27):
(greeting-list :greeting "how have you been?")
(HELLO THERE
"Jake" "how have you been?")
USER(28):
(greeting-list :greeting "how have you been?" :username
"Joe")
(HELLO THERE
"Joe" "how have you been?")
Note that with keyword arguments, you use a normal Symbol (i.e. without the preceding colon) in the definition of the Function, but a keyword Symbol as the "tag" when calling the Function. There is a logical reason behind this, which you will understand soon.
Input and Output in CL is done through objects called streams. A Stream is a source or destination for pieces of data which generally is connected to some physical source or destination, such as a terminal console window, a file on the computer's hard disk, or a web browser. Each thread of execution in CL defines global parameters which represent some standard Streams:
Read is the standard mechanism for reading data items into CL. Read takes a single optional argument for its Stream, and reads a single data item from that Stream. The default for the Stream is *standard-input*. If you simply type (read) at the Command Prompt, CL will wait for you to enter some valid Lisp item, and will return this item. Read simply reads, it does not evaluate, so there is no need to quote the data being read. You can set a variable to the result of a call to read:
(setq myvar (read))
The Function read-line is similar to read, but will read characters until it encounters a new line or end-of-file, and will return these characters in the form of a String. Read-line is appropriate for reading data from files which are in a "line-based" format rather than formatted as Lisp expressions.
Print and prin1 each take exactly one CL object and output its printed representation to a Stream, which defaults to *standard-output*. Print puts a space and a newline after the item (effectively separating individual items with whitespace), and Prin1 outputs the item with no extra whitespace:
USER(29): (print 'hello)
HELLO
HELLO
In the above example, you see the Symbol HELLO appearing twice: the first time is the output actually being printed on the console, and the second is the normal return-value of the call to print being printed on the console by the read-eval-print loop. Print and Prin1 both print their output readably, meaning that CL's read Function will be able to read the data back in. For example, the double-quotes of a String will be included in the output:
USER(30): (print "hello")
"hello"
"hello"
Princ, as distinct from print and prin1, prints its output in more of a human-readable format, which means for example that the double-quotes of a String will be stripped upon output:
USER(31):
(princ "hello")
hello
"hello"
in the above example, as before, we see the output twice: once when it is actually printed to the console, and again when the return-value of the call to princ is printed by the read-eval-print loop.
Format is a powerful generalization of prin1, princ, and other basic printing Functions, and can be used for almost all output. Format takes a Stream, a control-string, and optionally any number of additional arguments to fill in placeholders in the Control-string:
USER(33):
(format t "Hello There ~a" 'bob)
Hello There
BOB
NIL
In the above example, t is used as a shorthand way of specifying *standard-output* as the Stream, and ~a is a Control-string placeholder which processes its argument as if by princ. If you specify either an actual Stream or T as the second argument to format, it will print by side-effect and return NIL as its return-value. However, if you specify NIL as the second argument, format does not output to any Stream at all by side-effect, but instead returns a String containing what would have been output to a Stream if one had been provided:
USER(34):
(format nil "Hello There ~a" 'bob)
"Hello
There BOB"
In addition to ~a, there is a wealth of other Format Directives for an extensive choice of output, such as outputting items as if by prin1, outputting Lists with a certain character after all but the last item, outputting Numbers in many different formats including Roman Numerals and English words, etc.
In order to name directories and files on a computer filesystem in an operating-system independent manner, CL provides the pathname system. CL Pathnames do not contain system-specific Pathname Strings such as the forward-slash of Unix/Linux filenames or the backward-slash of MSDOS/Windows filenames. A CL Pathname is basically a structure which supports a constructor Function, make-pathname, and several accessor Functions, such as pathname-name, pathname-directory, and pathname-type. The Pathname structure contains six slots:
On standard
Unix filesystems, only the Directory, Name, and Type slots are relevant. On MSDOS/Windows
filesystems, the Device slot is also relevant. On the Symbolics platform, which
has a more advanced filesystem, the Version and Host slots also have relevance.
The constructor
Function, make-pathname, takes keyword arguments corresponding to the six possible slots of the
Pathname.
The following
is an example of creating a Pathname which would appear on Unix as /tmp/try.txt:
USER(15):
(defparameter *my-path*
(make-pathname
:directory (list :absolute "tmp")
:name
"readme"
:type
"txt"))
*MY-PATH*
USER(16):
*my-path*
#p"/tmp/readme.txt"
As seen in the above example, the :directory slot is technically a List, whose first is a keyword Symbol (either :absolute or :relative), and whose rest is the individual directory components represented as Strings.
Doing file input and output in CL is essentially a matter of combining the concepts of Streams and Pathnames. You must first open a file, which entails associating a Stream with the file. CL does provide the open Function, which directly opens a file and associates a Stream to it. For a number of reasons, however, generally you will use a Macro called with-open-file which not only opens the file, but automatically closes it when you are done with it, and performs additional cleanup and error-handling details for you. With-open-file takes a specification-list and a body. The Specification-list consists of a stream-variable (a Symbol) and a Pathname, followed by several optional keyword arguments which specify, for example, whether the file is for input, output, or both. Here is an example of opening a file and reading one item from it:
(with-open-file
(input-stream *my-path*)
(read
input-stream))
No extra keyword arguments are required in the Specification-list in this example, since "read-only" mode is the default for opening a file. In order to write to a file, additional keyword arguments must be given:
(with-open-file
(output-stream *my-path*
:direction
:output
:if-exists
:supersede
:if-does-not-exist
:create)
(format
output-stream "Hello There"))
CL provides
several other built-in data structures to meet a wide variety of needs. Arrays
and Hash Tables support the storage of values in a manner similar to Lists and
Plists, but with much faster look-up speed for large data sets. Structures
provide another Plist-like construct, but more efficient and structured.
Classes extend
the idea of Structures to provide a full object-oriented programming paradigm supporting
multiple inheritance, and Methods which can dispatch based on any number of
specialized arguments ("multimethods"). This collection of features in CL
is called the Common Lisp Object System, or CLOS.
A Hash Table is
similar to what in some other languages is known as an Associative Array. It is
comparable to a single-dimensional array which supports keys which are any
arbitrary CL object, which will match using eql, e.g. Symbols
or Integers. Hash Tables support virtually constant-time lookup of data,
which means that the time it takes to look up an item in a Hash Table will
remain stable, even as the number of entries in the Hash Table increases.
To work with
Hash Tables, CL provides the constructor Function make-hash-table and the accessor
Function gethash. In order to set entries in a Hash Table, use the setf operator in conjunction
with the gethash accessor Function. Setf is a generalization of setq which can be used
with places resulting from a Function call, in addition to exclusively with Symbols as
with setq.
Here is an
example of creating a Hash Table, putting a value in it, and retrieving the
value from it:
USER(35):
(setq os-ratings (make-hash-table))
#<EQL
hash-table with 0 entries @ #x209cf822>
USER(36):
(setf (gethash :windows os-ratings) :no-comment)
:NO-COMMENT
USER(37):
(gethash :windows os-ratings)
:NO-COMMENT
T
Note that the gethash Function
returns two values. This is the first example we have seen of a Function which returns
more than one value. The first return-value is the value corresponding to the
found entry in the Hash Table, if it exists. The second return-value is a
Boolean value (i.e. T or NIL), indicating whether the value was actually found
in the Hash Table. If the value is not found, the first return-value will be NIL and the second
return-value will also be NIL. This second value is necessary to distinguish from cases when the entry is found in the
Hash Table, but its value just happens to be NIL.
There are
several ways to access multiple return-values from a Function, e.g. by using multiple-value-bind
and
multiple-value-list.
Arrays are data structures which can hold single- or multi-dimensional "grids" full of values, which are indexed by one or more Integers. Like Hash Tables, Arrays support very fast access of the data based on the Integer indices. You create Arrays using the constructor Function make-array, and refer to elements of the Array using the accessor Function aref. As with Hash Tables, you can set individual items in the Array using setf:
USER(38):
(setq my-array (make-array (list 3 3)))
#2A((NIL NIL
NIL) (NIL NIL NIL) (NIL NIL NIL))
USER(39):
(setf (aref my-array 0 0) "Dave")
"Dave"
USER(40):
(setf (aref my-array 0 1) "John")
"John"
USER(41):
(aref my-array 0 0)
"Dave"
USER(42):
(aref my-array 0 1)
"John"
The make-array Function also supports a myriad of optional keyword arguments for initializing the array at the time of creation.
Structures support creating your own data structures with named slots, similar to, for example, the Pathname data structure which we have seen. Structures are defined with the Macro defstruct, which supports many options for setting up the instance constructor Functions, slots, and accessor Functions of the Structure. For a complete treatment of Structures, we refer you to one of the CL references or to the on-line documentation for defstruct.
Classes and
Methods form the core of the Common Lisp Object System (CLOS), and add
full-blown object-oriented capabilities to Common Lisp.
A complete
treatment of CLOS is beyond the scope of this booklet, but we will provide a
brief overview. Classes are basically "blueprints" or "prototypes" for Objects. Objects, in this
context, are a kind of structure for grouping together both data and
computational functionality. Methods are very similar to Functions, but they can
be specialized to apply to particular combinations of Object types.
CLOS is a generic
function object-oriented system, which means that Methods exist independently of
Classes (i.e. Methods do not ¡®belong to" Classes). Methods can take different
combinations of Class instance types as arguments, however, and Methods can be
specialized based upon the particular combinations of types of their arguments.
Simple CL datatypes, such as Strings and Numbers, are also considered as
Classes.
Here is an
example of defining a Class and a Method, making an instance of the Class, and calling
the Method using the instance as the argument:
USER(43):
(defclass box () ((length :initform 10)
(width :initform 10)
(height :initform 10)))
#<STANDARD-CLASS
BOX>
USER(44):
(defmethod volume ((self box))
(* (slot-value self 'length)
(slot-value self 'width)
(slot-value self 'height)))
#<STANDARD-METHOD
VOLUME (BOX)>
USER(45):
(setq mybox (make-instance 'box))
#<BOX @
#x209d135a>
USER(46):
(volume mybox)
1000
As seen in the
above example, the Class definition takes at least three arguments: a Symbol
identifying the name of the Class, a Mixin-list which names superclasses (none,
in this example), and a List of slots for the Class, with default values
specified by the :initform keyword argument.
The Method
definition is very much like a Function definition, but its arguments (single
argument in the case of this example) are specialized to a particular Class
type. When we call the Method, we pass an instance of the Class as an argument,
using a normal argument List. This is unlike Java or C++, where a Method is
considered to be "of" a particular Class instance, and only additional arguments
are passed in an argument List.
CL is more
consistent in this regard.
Because normal
CL data types are also considered to be Classes, we can define Methods which
specialize on
them as well:
USER(47):
(defmethod add ((value1 string) (value2 string))
(concatenate 'string value1 value2))
#<STANDARD-METHOD
ADD (STRING STRING)>
USER(48):
(defmethod add ((value1 number) (value2 number))
(+ value1 value2))
#<STANDARD-METHOD
ADD (NUMBER NUMBER)>
USER(49): (add
"hello " "there")
"hello
there"
USER(50): (add
3 4)
7
Typically,
Symbols in CL exist within a particular Package. You can think of Packages as
"Area Codes" for Symbols. They are namespaces used within CL to help avoid
name clashes.
Package names
are generally represented by Keyword Symbols (i.e. Symbols whose names are preceded
by a colon). ANSI CL has a "at" Package system, which means that Packages
do not "contain" other Packages in a hierarchical fashion, but you can
achieve essentially the same effect by "layering" Packages. Actually,
hierarchical Packages have been added to Allegro CL as an extension to the ANSI
Standard, and may be added to the ANSI standard at some point. However, the
real use of hierarchical Packages is to avoid name clashes with Package names
themselves. You can do this in any case simply by separating the components of
a Package name with a delimiter, for example, a dot: :com.genworks.books.
In a given CL
session, CL always has a notion of the current Package which is stored
in the (dynamic) variable *package*. It is important always to be aware of what
the current Package is, because if you are in a different Package than you
think you are, you can get very surprising results. For example, Functions that
are defined in :package-1 will not necessarily be visible in :package-2, so if you
attempt to run those Functions from :package-2, CL will think they are
undefined.
Normally the CL
Command Prompt prints the name of the current Package. You can move to a different
Package, e.g. foo, with the toplevel comand :package :foo.
Small programs can be
developed in the :user Package, but in general, a large application should be developed in its own
individual Package.
Packages can export Symbols to make
them accessible from other Packages, and import Symbols to make
them appear to be local to the Package.
If a Symbol is
Exported from one Package but not Imported into the current Package, it is
still valid to refer to it. In order to refer to Symbols in other Packages,
qualify the Symbol with the Package name and a single colon: package-2:foo.
If a Symbol is
not Exported from the outside Package, you can still refer to it,
but this would represent a style violation, as signified by the fact that you
have to use a double-colon: package-2::bar.
We have seen many occurences of Symbols whose names are preceded by a colon (:). These Symbols actually reside within the :keyword Package, and the colon is just a shorthand way of writing and printing them. Keyword Symbols are generally used for enumerated values and to name (Keyword) Function arguments. They are "immune" to Package (i.e. there is no possibility of confusion about which Package they are in), which makes them especially convenient for these purposes.
This section discusses common errors and stumbling blocks that new CL users often encounter.
One common pitfall is not understanding when to quote expressions. Quoting a single Symbol simply returns the Symbol, whereas without the quotes, the Symbol is treated as a variable:
USER(6): (setq
x 1)
1
USER(7): x
1
USER(8): 'x
X
Quoting a List returns the List, whereas without the quotes, the List is treated as a Function (or Macro) call:
USER(10): '(+
1 2)
(+ 1 2)
USER(11):
(first '(+ 1 2))
+
USER(12): (+ 1
2)
3
USER(13):
(first (+ 1 2))
Error: Attempt
to take the car of 3 which is not listp.
When you define a Function with defun, the arguments go inside their own List:
USER(19):
(defun square (num)
(* num num))
SQUARE
But when you call the Function, the argument List comes spliced in directly after the Function name:
USER(20):
(square 4)
16
A common mistake is to put the argument List into its own List when calling the Function, like this:
USER(21)
(square (4))
Error: Funcall
of 4 which is a non-function.
If you are used to programming in Perl or Java, you will likely do this occasionally while you are getting used to CL syntax.
Another point of confusion is the difference between Symbols and Strings. The Symbol AAA and the String "AAA" are treated in completely different ways by CL. Symbols reside in Packages; Strings do not. Moreover, all references to a given Symbol in a given Package refer to the same actual address in memory - a given Symbol in a given Package is only allocated once. In contrast, CL might allocate new memory whenever the user defines a String, so multiple copies of the same String of characters could occur multiple times in memory. Consider this example:
USER(17):
(setq a1 'aaa)
AAA
USER(18):
(setq a2 'aaa)
AAA
USER(19): (eql
a1 a2)
T
EQL compares actual Pointers or Integers - A1 and A2 both point to the same Symbol in the same part of memory.
USER(20):
(setq b1 "bbb")
"bbb"
USER(21):
(setq b2 "bbb")
"bbb"
USER(22): (eql
b1 b2)
NIL
USER(23):
(string-equal b1 b2)
T
Again, EQL compares
pointers, but here B1 and B2 point to different memory addresses, although
those addresses happen to contain the same String. Therefore the comparison by string-equal, which
compares Strings character-by-character, rather than the pointers to those
Strings, returns T.
Another difference
between Symbols and Strings is that CL provides a number of operations for manipulating
Strings that do not work for Symbols:
USER(25):
(setq c "jane dates only lisp programmers")
"jane
dates only lisp programmers"
USER(26):
(char c 3)
¡¬e
USER(27):
(char 'xyz 1)
Error: `XYZ'
is not of the expected type `STRING'
USER(29):
(subseq c 0 4)
"jane"
USER(30):
(subseq c (position ¡¬#space c))
" dates
only lisp programmers"
USER(31):
(subseq c 11 (length c))
"only
lisp programmers"
CL has several
different predicates for testing equality. This is demanded because of the many
different object types in CL, which dictate different notions of equality.
A simple rule
of thumb is:
Examples:
USER(33):
(setq x :foo)
:FOO
USER(34): (eql
x :foo)
T
USER(35): (=
100 (* 20 5))
T
USER(36): (string-equal
"xyz" "XYZ")
T
USER(37):
(setq abc '(a b c))
(A B C)
USER(38):
(equal abc (cons 'a '(b c)))
T
USER(39):
(equal ¡¬#a (char "abc" 0))
T
USER(40): (eql
abc (cons 'a '(b c)))
NIL
The last
expression returns NIL because, although the two Lists have the same contents,
they reside in different places in memory, and therefore the pointers to those
Lists are different.
Note that
several of the Functions which by default use eql for matching
will take an optional keyword argument test which can
override this default:
USER(54):
(member "blue" '("red" "blue" "green"
"yellow")
:test #'string-equal)
("blue"
"green" "yellow")
Recall from Section 3.2.7 that member returns the rest of the List starting at the found element.
Since Macros and Functions both occur as the first element in an expression, at first glance it may appear difficult to distinguish them. In practice, this rarely becomes an issue. For beginners in the language, the best approach is to make a default assumption that something is a Function unless you recognize it as a Macro. Most Macros will be readily identifiable because one of the following will apply:
Beyond these general rules of thumb, if you find yourself in doubt about whether something is a Function or a Macro, you can:
1. Check with on-line reference documentation or in a CL reference book (see the Bibliography in Appendix A)
2. Use the symbol-function Function to ask your CL session, like this:
USER(24): (symbol-function
'list)
#<Function
LIST>
USER(25):
(symbol-function 'with-open-file)
#<macro
WITH-OPEN-FILE @ #x2000b07a>
Because CL
programs do not have to free and allocate memory explicitly, they can be
written much more quickly. As an application evolves, some operations which
"cons" can be replaced by operations which "recycle" memory, reducing
the amount of work which the garbage collector (CL's automatic memory
management subsystem) has to do.
In order to
write programs which conserve on garbage collecting, one technique is simply to
avoid the unnecessary use of operations which cons. This ability will come with
experience.
Another
technique is to use destructive operations, which are best
used only after one has gained more experience working with CL. Destructive
operations are for the most part beyond the scope of this booklet.