Sections 1.4 and 1.5 of our textbook describe a way to collect any number of data into a structure that can be treated as a whole. A list is a sequence of data assembled in this way.
There are three basic ways to create lists. One is to write out a literal constant -- a numeral, a symbol, or a double-quoted string literal, for instance -- for each datum, separating them with spaces, and then to enclose the whole thing in parentheses and attach a single quotation mark to the front. The value of the expression
'(38 72 apple -1/3 "Washington Irving")
is a five-element list consisting of two numbers, a symbol, another number, and finally a string. As a special case, the value of the expression
'()
is an empty list -- a sequence with no elements, a container with nothing in it.
In these cases, the single quotation mark must be present so that Scheme
does not misinterpret the left parenthesis as the beginning of a procedure
call. The lists created in the last two examples wouldn't work as
procedure calls, since 38 is not a procedure and the empty
list doesn't have anything after the left parenthesis that could even hint
at what procedure to call. In these cases, the single quotation mark makes
the difference between a correctly formed Scheme expression and erroneous
input. In other cases, the single quotation mark 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 literal constant denoting a
list of three elements -- the symbol + and the numbers 5 and
3.
A second way to create a list is to call the procedure named
list. This procedure takes all of its arguments (the values
expressed by the operands), however many of them there may be, and bundles
them into a list. 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 "Washington Irving") (38 72 apple -1/3 "Washington Irving")
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 call the list procedure to create an empty list?
The third way to create a list is to call a ``construction'' procedure
named cons, which takes exactly two arguments, the second of
which is ordinarily a previously created list. The effect of
cons is to create a new list, just like the previously created
one except that the first argument has been added at the beginning of the
list:
> (define base (list 72 'apple -1/3 "Washington Irving")) > (cons 38 base) (38 72 apple -1/3 "Washington Irving")
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
string "first" as its first and only element. (Hint: Have
cons prepend this string to an empty list.)
The expression
(cons 'alpha (cons 'beta (cons 'gamma '(delta epsilon))))
creates a list. How many elements does it have? Check your answer by asking Scheme to evaluate this expression.
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 '(38 72 apple -1/3 "Washington Irving")) 38 > (cdr '(38 72 apple -1/3 "Washington Irving")) (72 apple -1/3 "Washington Irving)
If you want the second rather than the first element of a list, you can
combine car and cdr to extract it:
> (define sample (list 38 72 'apple -1/3 "Washington Irving")) > (car (cdr sample)) 72
The idea is that the procedure call (cdr sample) computes a
list just like sample except that the 38 is gone,
and then car gives you the first element of that computed
list. Similarly, (car (cdr (cdr sample))) is the third
element of sample, and so on.
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 compute the length of the list '(alpha beta (gamma-1
gamma-2) delta).
Use Scheme to compute the length of the empty list.
Write a Scheme procedure call to create 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 '(alpha beta (gamma-1
gamma-2) delta). Does Scheme reverse the order of lists that are
elements of the main list?
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 the list '(alpha
beta) and the list '(1 2 3). How many elements does
the resulting list have?
Write a call to the procedure list, applying it to the list
'(alpha beta) and the list '(1 2 3). How many
elements does the resulting list have? Why is the answer to this question
different from the answer to the question at the end of exercise 10?
Write a call to the procedure cons, applying it to the list
'(alpha beta) and the list '(1 2 3). 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 10 and 11?
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 "Washington
Irving") -- namely, the number -1/3.
A Boolean value is a datum that reflects the outcome of a single
yes-or-no test. For instance, if one were to ask Scheme to compute whether
the empty list has five elements, it would be able to determine that it
does not, and it would signal this result by displaying the Boolean value
for ``no'' or ``false,'' which is #f. There is only one other
Boolean value, the one meaning ``yes'' or ``true,'' which is
#t. (These are called ``Boolean values'' in honor of the
logician George Boole, who was the first to develop a satisfactory formal
theory of them.)
A predicate is a procedure that always returns a Boolean value. A
procedure call in which the procedure is a predicate performs some
yes-or-no test on its arguments. For instance, the predicate
number? -- the question mark is part of the name of the
procedure -- takes one argument and returns #t if that
argument is a number, #f if it does not. Similarly, the
predicate even? takes one argument, which must be an integer,
and returns #t if the integer is even and #f if
it is odd. The names of most Scheme predicates end with question marks,
and I recommend this useful convention even though it is not required by
the rules of the programming language.
Here is a selection of useful predicates:
number? tests whether its argument is a number.symbol? tests whether its argument is a symbol.procedure? tests whether its argument is a
procedure.list? tests whether its argument is a list.null? tests whether its argument is the empty list.boolean? tests whether its argument is a Boolean
value.eq? tests whether its two arguments are identical, in
the very narrow sense of occupying the same storage location in the
computer's memory. In practice, this is useful information only if at
least one argument is known to be a symbol or a Boolean value.eqv? tests whether its two arguments ``should
normally be regarded as the same object'' (as the language standard
declares). Note, however, that two lists can have the same elements
without being ``regarded as the same object'' and that in Scheme's view the
number 5, which is ``exact,'' is not necessarily the same object as the
number 5.0, which might be an approximation.equal? tests whether its two arguments are the same or,
in the case of lists, whether they have the same contents.= tests whether its arguments, which must all be
numbers, are numerically equal; 5 and 5.0 are numerically equal for this
purpose.< tests whether its arguments, which must all be
numbers, are in strictly ascending numerical order.> tests whether its arguments, which must all be
numbers, are in strictly descending numerical order.<= tests whether its arguments, which must all be
numbers, are in ascending numerical order, allowing equality.>= tests whether its arguments, which must all be
numbers, are in descending numerical order, allowing equality.even? tests whether its argument, which must be an
integer, is even.odd? tests whether its argument, which must be an
integer, is odd.zero? tests whether its argument, which must be a
number, is equal to zero.positive? tests whether its argument, which must be a
real number, is positive.negative? tests whether its argument, which must be a
real number, is negative.
Call each of the predicates on the preceding list twice -- once with an
argument that ensures that the value of the procedure call is
#t, once with an argument that makes the value #f.
Another useful Boolean procedure is not, which takes one
argument and returns #t if the argument is #f and
#f if the argument is anything else. For example, one can
test whether the square root of 10 is unequal to the natural logarithm of
12 by giving the command
(not (= (sqrt 10) (log 12)))
If Scheme says that the value of this expression is #t, then
the two numbers are indeed unequal.
A few lines up, I said that not is a procedure in Scheme.
What procedure could you call to find out whether this assertion is true?
The symbol not is the name of the procedure discussed
in the preceding exercise, but the symbol itself, considered as a datum, is
not a procedure. Does Scheme agree with this classification? How
could one ask Scheme whether the symbol not is a
procedure?
This document is available on the World Wide Web as
http://www.math.grin.edu/courses/Scheme/spring-1998/lists.html
created September 2, 1997
last revised June 21, 1998