As you've no doubt read in section 1.3 of Scheme and the art of programming, Scheme is not limited to numerical computation, but can also operate on pure symbols. When we want to refer to a symbol as a value involved in a computation, rather than as the name of some other value, we put an apostrophe (usually pronounced ``quote'') in front of it. In effect, by quoting the symbol, we're telling Scheme to take it literally and without further interpretation or evaluation:
> 'sample sample
In addition to ``unstructured'' data types such as symbols and numbers, Scheme supports lists, which are structures that contain other values as elements. There is one list -- the empty list -- that contains no elements at all. Any other list is constructed by attaching some value, called the car of the list, to a previously constructed list, which is called the cdr of the new list. (There is a semi-reasonable explanation for these apparently arbitrary names, but it's clearly historical-sidebar material.)
Scheme's name for the empty list is a pair of parentheses with nothing
between them: (). When we refer to the empty list in a Scheme
program, we have to put an apostrophe before the left parenthesis, so that
Scheme won't mistake the parentheses for a procedure call:
> '() ()
Since this conventional name for the empty list is not very readable, our
implementation of Scheme also provides a built-in name, null,
for the empty list. I follow this usage and recommend it.
> null ()
The ``constructor'' procedure for non-empty lists is called
cons. It takes two arguments and returns a list that is just
like the second argument, except that the first argument has been added at
the beginning, as a new first element. By repeated applications of
cons, we can build up a list of any size:
> (define singleton (cons 'sample null)) > singleton (sample) > (define doubleton (cons 'another-element singleton)) > doubleton (another-element sample) > (define tripleton (cons 'yet-another-element doubleton)) > tripleton (yet-another-element another-element sample) > (cons 'senior (cons 'junior (cons 'sophomore (cons 'freshling null)))) (senior junior sophomore freshling)
The cons procedure never returns an empty list, since it
always adds an element at the beginning of another list.
Call the cons procedure to create a list that has the number 1
as its first and only element.
Describe the value of the expression
(cons 'alpha (cons 'beta (cons 'gamma (cons 'delta null))))
and check your answer by asking DrScheme to evaluate this expression.
There are two other ways to create lists. One is to write out a literal constant -- a numeral or a symbol -- for each datum, separating them with spaces, and then to enclose the whole thing in parentheses and attach an apostrophe at the beginning. The value of the expression
'(38 72 apple -1/3 sample)
is a five-element list consisting of two numbers, a symbol, another number, and finally another symbol. Note that the apostrophe blocks the evaluation of the whole list, so that it is not necessary to quote separately the symbols that occur as elements of the list.
In a list literal like this one, the apostrophe must be present so
that Scheme does not misinterpret the left parenthesis as the beginning of
a procedure call. Sometimes that apostrophe is all that distinguishes two
different, correctly formed expressions. For instance,
(+ 5 3) is a procedure call that has the value 8,
whereas '(+ 5 3) is a list literal denoting a list
of three elements -- the symbol + and the numbers 5 and 3.
> (+ 5 3) 8 > '(+ 5 3) (+ 5 3)
Yet another way to create a list is to invoke a procedure named
list. This procedure takes all of its arguments, however many
of them there may be, and packs them into a list. (Behind the scenes,
list invokes cons once for each element of the
completed list, to hook that element onto the previously created cdr.)
Just as the addition procedure + sums its arguments and
returns the result, so the list procedure collects its
arguments and returns the resulting list:
> (list 38 72 'apple -1/3 'sample) (38 72 apple -1/3 sample)
Start Scheme and call the procedure list, supplying the
numerals 17 and 43 as operands. Describe the
value returned by the procedure.
How would you call the list procedure to create a list
containing the symbols alpha, beta, and
gamma, in that order?
How would you invoke the list procedure to create an empty
list?
Is it possible to create a list in which the same element occurs more than once? Find out by experiment.
It is possible, and indeed common, for a list to be an element of another list. For instance, the expression
(list 'alpha 'beta (list 'gamma-1 'gamma-2) 'delta)
creates a four-element list: Its first element is the symbol
alpha, its second is the symbol beta, its third
is a two-element list comprising the symbols gamma-1 and
gamma-2, and its fourth is the symbol delta.
It is possible for all of the elements of a list to be lists. It is possible for a list that is an element of another list to have lists as its elements, and so on -- lists can be embedded within lists to any desired level of nesting. This idea is subtler and more powerful than it may initially seem to be.
To recover elements from a list, one commonly uses the built-in Scheme
procedures car, which takes one argument (a non-empty list)
and returns its first element, and cdr, which takes one
argument (a non-empty list), and returns a list just like the one it was
given, except that the first element has been removed. In a sense,
car and cdr are the inverses of
cons; if you think of a non-empty list as having been
assembled by a call to the cons procedure, car
gives you back the first argument to cons and cdr
gives you back the second one.
> (car (cons 'apple (cons 'orange null))) apple > (cdr (cons 'apple (cons 'orange null))) (orange)
If you want the second rather than the first element of a list, you can
combine car and cdr to extract it:
> (define sample (cons 'apple (cons 'orange null))) > (car (cdr sample)) orange
The idea is that the procedure call (cdr sample) computes a
list just like sample except that the symbol
apple is gone, and then car gives you the first
element of that computed list. Similarly, (car (cdr (cdr
longer-list))) is the third element of longer-list, and
so on.
What is the cdr of a one-element list?
It makes no sense to apply the car and cdr
procedures to an empty list, because there's no way to split off the
``first element'' of a list that has no elements. What happens if you try
it anyway? Find out by having DrScheme evaluate a deliberately incorrect
procedure call.
Just as Scheme provides many built-in procedures that perform simple operations on numbers, there are several built-in procedures that operate on lists. Here are four that are very frequently used:
The length procedure takes one argument, which must be a
list, and computes the number of elements in the list. (An element that
happens to be itself a list nevertheless contributes 1 to the total that
length computes, regardless of how many elements it happens
to contain.)
Use Scheme to give the name Greek-letters to the list
constructed by the expression (list 'alpha 'beta (list 'gamma-1
'gamma-2) 'delta). Then call the length procedure to
confirm that it has four elements.
Determine the length of the empty list.
Write a Scheme procedure call to create a list of length 5. (For this exercise, I don't care what elements you put into the list.) Check your answer by having Scheme compute the length of that list.
The reverse procedure takes a list and returns a new list
containing the same elements, but in the opposite order.
Use Scheme to compute the reversal of the list whose elements are the
symbols senior, junior, sophomore,
and freshling, in that order.
If a list has another list as one of its elements, does
reverse reverse that inner list as well as the outer one?
Find out by experiment.
The append procedure takes any number of arguments, each of
which is a list, and returns a new list formed by stringing together all of
the elements of the argument lists, in order, to form one long list.
Use Scheme to find the result of stringing together a list with the symbols
alpha and beta as its elements and a list with
the numbers 1, 2, and 3 as its elements. How many elements does the
resulting list have?
Invoke the procedure list, applying it to the two lists that
you strung together in exercise 14: a list with the symbols
alpha and beta as its elements and a list with
the numbers 1, 2, and 3 as its elements. How many elements does the
resulting list have? The answer to this question is different from the
answer to the question at the end of exercise 14 -- why?
Write a call to the procedure cons, applying it to our
favorite two lists: a list with the symbols alpha and
beta as its elements and a list with the numbers 1, 2, and 3
as its elements. How many elements does the resulting list have? Why is
the answer to this question different from the answers to the questions at
the end of exercises 14 and 15?
The list-ref procedure takes two arguments, the first of which
is a list and the second a non-negative integer less than the length of the
list. It recovers an element from the list by skipping over the number of
initial elements specified by the second argument (applying cdr
that many times) and extracting the next element (by invoking
car). So (list-ref sample 0) is the same as
(car sample), (list-ref sample 1) is the same as
(car (cdr sample)), and so on.
Write a call to the list-ref procedure that will extract the
fourth element of the list (38 72 apple -1/3 sample) --
namely, the number -1/3.
This document is available on the World Wide Web as
http://www.cs.grinnell.edu/~stone/courses/scheme/symbols-and-lists.html
created September 2, 1997
last revised March 31, 2000