Table of Contents

5. Lists

The primary compound data structure provided by Scheme is the list. We've been using lists the whole time—they're written as a sequence of values surrounded by parentheses.

It is Scheme's syntax (rules of how code must be written) that specifies that the first element of a list should be a function that will be called. This is the behavior we've seen so far—lists as code.

To create a value that is a list with data, we use the list function:

(list 3 5 12)

Notice what appears in the console: (3 5 12). This is the printed representation of the list with the elements 3, 5, and 12.

If we try evaluating it, we get an error, because Scheme thinks we are trying to call the function named 3.

(3 5 12)

Remember (define ...)? Let's define an arbitrary short lucky-numbers list:

(define lucky-numbers (list 7 21 32 51 55 60))

The size of a list can be obtained with length:

(length lucky-numbers)

A shortcut for writing lists

If you're manually writing many lists, there's a shortcut called quote that tells Scheme not to try evaluating the value following quote. For example:

(quote ("apples" "bananas" "oranges"))

It looks like list, but there are some differences. For now, both ways of making lists will work. A value created with quote should not be modified, but this will not happen in this book.

So far, I've just replaced list with quote. The shortcut is that (quote value) may be written as 'value. This is a "straight" quote ('), not a "backquote" (`). Be careful not to mix them up.

So the code above is the same as:

'("apples" "bananas" "oranges")

quote happens to be a special form, which use reserved words. Special forms look like functions, but behave according to special rules. If quote were a function, the list would have had to be evaluated, and you would have gotten an error.

Generating evenly spaced numbers

The iota function creates a list with a given number of elements, starting from a specific number and with a specific step between adjacent numbers.

The simplest way of using iota is passing a single number. This many numbers will be created, starting from 0. (Most modern programming languages start counting from 0).

(iota 5)

If you add another argument, say 10, the resulting list will start at 10. (five numbers, starting from 10 and stepping by 1).

(iota 5 10)

Finally, passing three numbers will specify the step as well:

(iota 5 10 30)

Iterating over a list

It would be impractical to iterate (repeatedly take an element one by one) over a long list. Of course, we wish to do something with each value. Remember that functions are actions that operate on values.

The function for-each takes a function name (for example, f) as the first argument, followed by lists. The number of lists you need to pass depends on how many arguments f takes.

It sounds confusing, so let's look at some examples.

The simplest thing you can do to a value is print it.

(define players '("Adam" "Beth" "Cathy"))
(for-each print players)

Look at the output in the console. Each player's name was printed, one per line.

Let's take a look at some code that creates a drawing of concentric circles (they share the same center).

(define (centered-circle radius)
  (circle 0 0 radius))

(clear)
(set-stroke! "SkyBlue")
(for-each centered-circle (iota 4 10 50))

The first part defines the centered-circle function, then the graphics box is cleared, a color is chosen, and the list of radii (10 60 110 160) is passed to for-each so that it knows what sized centered-circle to draw!

Above, I've said that it matters how many arguments are accepted by the function passed to for-each. We'll use the rectangle-drawing function, rect, as another argument to for-each.

Let's make all rectangles' bottom-left corner fixed at (0, 0). So we will only vary their widths and heights. We want to vary the width greatly, but the heights, less so:

(define (fixed-rect w h)
  (rect 0 0 w h))

(clear)
(axes)
(set-stroke! "SkyBlue")
(define widths '(30 70 110 170))
(define heights '(10 15 25 35))
(for-each fixed-rect widths heights)

What is happening is that fixed-rect operates initially on the first element of widths and the first element of heights. Then, it moves on to the second elements, and so on.

We can achieve the same drawing without for-each, by drawing the four rectangles with their correct size. The color has been changed so you can compare the two versions:

(clear)
(axes)
(set-stroke! "Maroon")
(rect 0 0 30 10)
(rect 0 0 70 15)
(rect 0 0 110 25)
(rect 0 0 170 35)

With a few items, the amount of code is nearly the same. However, consider applying a function to all 360 pixels of the x-axis. Certainly you will want a concise manner of doing so!

Table of Contents