Scheme Programming/List Operations
Lists are a fundamental data structure in Scheme, as they are in many other functional languages. While among the simplest of structures, they have a rich set of operations and an amazing variety of uses. In this section, we'll cover the basics of creating and using lists.
Scheme Lists
editA list is a sequence of elements ending with ()
, sometimes called
the "empty list" or "nil". We can build up lists with cons
, which
attaches a new element to the head of a list:
> '() ; The empty list must be quoted.
()
> (cons 3 '())
(3)
> (cons 2 (cons 3 '()))
(2 3)
> (cons #t (cons 2 (cons 3 '())))
(#t 2 3)
The first argument of cons
may be any Scheme object, and
the second is a list; the value of (cons x xs)
is a new list which
contains x
followed by the elements of xs
.[1] (Scheme's way of
printing lists uses a shorthand which hides the final ()
. In
the responses above, we simply see the elements of a list enclosed in
parentheses.) It's important to note that we must always quote ()
,
in order to prevent Scheme from trying to interpret it as an (invalid)
expression.
Two predicates define the Scheme list type. null?
,
is true for ()
(the empty list) and is false otherwise, while
pair?
returns true only for objects constructed with
cons
.[2]
There are two basic functions for accessing the elements of lists. The first,
car
,
extracts the first element of a list:
> (car (cons 1 '()))
1
> (car (cons 2 (cons 3 '())))
2
> (car '())
; Error!
Applying car
to the empty list causes an error.
The second function, cdr
, gives us the tail of a list: that is, the
list without its leading element:
> (cdr (cons 1 '()))
()
> (cdr (cons 2 (cons 3 '())))
(3)
> (cdr '())
; Error!
As with car
, trying to apply cdr
to the empty list causes an error.
The three functions cons
, car
, and cdr
satisfy some useful
identities. For any Scheme object x and list xs, we have
(car (cons x xs)) = x
(cdr (cons x xs)) = xs
For any non-empty list ys, we also have
(cons (car ys) (cdr ys)) = ys
Clearly, building a list through successive cons
operations
is clumsy. Scheme provides the built-in function list
which
returns a list of its arguments:
> (list 1 2 3 4)
(1 2 3 4)
> (cons 1 (cons 2 (cons 3 (cons 4 '())))) ; equivalent, but tedious
(1 2 3 4)
list
can take any number of arguments.
We can write a range of useful functions using just cons
, car
,
and cdr
. For example, we can define a function to compute the length of
a list:
(define (length xs)
(if (null? xs)
0
(+ 1 (length (cdr xs)))))
This definition, like the definitions of most list operations, closely follows
the recursive structure of a list. There is a case for the empty
list (which is assigned the length 0), and a case for a non-empty list (the length
of the tail of the list, plus one). In practice, Scheme provides
length
as a built-in function.
Here's another simple example:
(define (find-number n xs)
(if (null? xs)
#f
(if (= n (car xs))
#t
(find-number n (cdr xs)))))
find-number
returns #t
if the argument n occurs in the list
xs, and returns #f
otherwise. Once again, this definition follows
the structure of the list argument. The empty list has no elements,
so (find-number n '())
is always false. If xs
is non-empty, then
n could be the first element, or it could be somewhere in the tail.
In the former case, find-number
returns true immediately. In the
latter, the return value should be true if n appears in (cdr xs)
and false otherwise. But since this is the definition of find-number
itself, we have a recursive call with the new argument (cdr xs)
.
Notes
edit- ↑ This
is a simplified account which is correct for our current purposes. In fact,
cons
constructs pairs of its arguments, both of which may be arbitrary Scheme objects. A pair whose second element is()
or another pair is called a list; all other pairs are called improper. - ↑ The definition of the Scheme list type is
looser than that of numbers, booleans, etc.
While the (scheme list) library provides a true
list?
function, this function is defined in terms ofnull?
andpair?
. See the previous note.