Lists, Booleans, and predicates

Constructing lists

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

Exercise 1

Start Scheme and call the procedure list, supplying the numerals 17 and 43 as operands. Describe the value returned by the procedure.


Exercise 2

How would you call the list procedure to create a list containing the symbols alpha, beta, and gamma, in that order?


Exercise 3

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.


Exercise 4

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


Exercise 5

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.

Taking lists apart

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.

Computing with lists

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


Exercise 6

Use Scheme to compute the length of the list '(alpha beta (gamma-1 gamma-2) delta).


Exercise 7

Use Scheme to compute the length of the empty list.


Exercise 8

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.


Exercise 9

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.


Exercise 10

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?


Exercise 11

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?


Exercise 12

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.


Exercise 13

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.


Booleans and predicates

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:


Exercise 14

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.


Exercise 15

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?


Exercise 16

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/~stone/courses/scheme/lists.html

created September 2, 1997
last revised May 27, 1998

John David Stone (stone@math.grin.edu)