Chapter 9. (The Values of Loops)

#|

Lists are a central data type in Racket. We started off manipulating them manually. Then we learned about lambda and functions like map and foldl. Now we will introduce a third and even more powerful way of managing lists: for loops.

|#

If you have taken a run-of-the-mill course on programming or read one of those standard introductory books, you know about for loops. Racketeers have a special relationship with loops, and therefore Racket has many kinds of for loops. Each has its own uses and purposes. Anything you can do with map, foldl, foldr, filter, andmap, ormap, and so on, you can do with for loops.

The basic for loop looks and acts like the one you may already know:

> (for ([i '(1 2 3 4 5)])
    (display i))
12345

The clause immediately after the for keyword sets up variable bindings for the body of the loop. A binding consists of a variable paired with an expression that evaluates to a list. In this example, the variable is i and the expression is '(1 2 3 4 5). Racket sets the variable to each element of the list and for each one evaluates the body of the for loop—in this case, (display i). Here, the for loop iterates across the list '(1 2 3 4 5) and uses the display function to print each value.

image with no caption

The basic for loop is for effect only. Like for-each, it returns nothing. The preceding for loop example is equivalent to (for-each display '(1 2 3 4 5)). If we want to mimic map, which involves building or running over a list, we use a different kind of for loop:

> (for/list ([i '(1 2 3 4 5)])
   (/ 1 i))
'(1 1/2 1/3 1/4 1/5)

This for/list loop acts like for, but it takes the last value of each iteration and collects these values in a list. You could think of it as follows:

> (map (lambda (x) (/ 1 x)) '(1 2 3 4 5))
'(1 1/2 1/3 1/4 1/5)

One of the most powerful loops is for/fold. This loop accumulates a value much like foldr and foldl:

> (for/fold ([sqrs 0])
            ([i '(1 2 3 4 5 6 7 8 9 10)])
    (+ (sqr i) sqrs))
385

The value returned in each iteration is bound to sqrs, which is initialized to 0. In essence, what we wrote above is equivalent to this expression:

> (foldl (lambda (i sqrs) (+ (sqr i) sqrs))
         0
         '(1 2 3 4 5 6 7 8 9 10))

But let’s say we also wanted to keep track of the number of squares over 50. We could do it like this:

> (for/fold ([sqrs 0]
             [count 0])
            ([i '(1 2 3 4 5 6 7 8 9 10)])
     (values (+ (sqr i) sqrs)
             (if (> (sqr i) 50)
                 (add1 count)
                 count))))
385
3

Wait! Look over this example again because it is complicated. Actually, it isn’t just complicated—it uses a feature of Racket that you have never seen before: an expression that produces multiple values. This is so new that it deserves its own section.

The values function returns whatever you pass to it:

> (values 42)
42

It may seem silly to have a function that just returns what it is given. But the values function can take anything and any number of values, and it returns them all at once:

> (values 'this 'and-this 'and-that)
'this
'and-this
'and-that

These values can be caught with define-values, another way to define variables:

> (define-values (one two three) (values 'three 'two 'one))
> one
'three
> two
'two
> three
'one

Like values, define-values can work with any number of arguments. Like define, define-values gives names to values. As the example explains, define-values comes with a sequence of identifiers between parentheses. It expects its right-hand side to deliver that many values. When it gets the values, it gives them the specified names.

Here is an example with a more complicated right-hand side:

> (define-values (x y)
    (if (string=? (today) "tuesday")
        (values 10 20)
        (values 42 55)))
> x

We will let you figure out what the value of x is because we don’t know on what day of the week you are reading this book.

Let’s look at the for/fold example from 9.1 FOR Loops again:

> (for/fold ([sqrs 0]
             [count 0])
            ([i '(1 2 3 4 5 6 7 8 9 10)])
    (values (+ (sqr i) sqrs)
            (if (> (sqr i) 50)
                (add1 count)
                count)))
385
3

On closer observation, for/fold is a bit like both define-values and values. It sets up two sets of bindings. The first one determines how many values each loop iteration with the loop body must return and how many values the loop itself returns. As you may have guessed, the body of the for/fold loop may refer to these names. Here, the loop sets up two such bindings, initializing both to 0 and giving them the names sqrs and count. The loop body consists of a values expression that produces two values. For each iteration, the first value is the square of i added to sqrs, and the second value is count or (add1 count), depending on how large the square of i is. In the end, the loop produces 385 and 3. Do you understand why?

image with no caption

All for loops come with the ability to skip some iterations. In the part of the for loop where we specify the lists we are using, we can put in a #:when clause. Each #:when clause comes with an expression that is evaluated for every iteration. The iteration is performed only if the expression following #:when evaluates to true:

> (for/list ([i '(1 2 3 4 5)]
             #:when (odd? i))
    i)
'(1 3 5)
> (for/fold ([sum 0])
            ([i '(1 2 3 4 5)]
             #:when (even? i))
    (+ sum i))
6

In this manner, #:when clauses make for loops act like filter.

Why should you use Racket’s for loops? You should not use for loops just because you are familiar with their impoverished cousins from pedestrian languages. You should get to know Racket’s for loops because they are far more powerful than what you may have encountered. Racketeers are keenly aware of the advantages of lambda, map, andmap, filter, and friends, and therefore Racket provides many more kinds of for loops than you have ever seen. If you can wrap your head around them, you will write compact yet highly readable code.

One major benefit of for loops is that they easily run through a lot of lists at once. All you need to do is add more for clauses, and the for loop binds all of those expressions simultaneously and iterates over all of them in parallel. The loop ends as soon as the shortest list is exhausted:

> (for/list ([i '(1 2 3 4 5)]
             [j '(1 2 3 4)]
             [k '(5 4 3 2 1)])
    (list i j k))
'((1 1 5) (2 2 4) (3 3 3) (4 4 2))

Here, we bind i, j, and k to the elements of three separate lists. Because the shortest list—bound to j—has only four elements, while the others have five, the loop runs four times, ignoring the last elements of the lists bound to i and k.

Take a look at this example:

> (for/list ([i '(1 2 3 4 5)]
             [s '("a" "b" "c" "d" "e")]
             #:when (and (even? i) (string=? s "d")))
    i)
'(4)

As you can see, it evaluates the loop body, i, only when i is 4 and s is the string "d".

Something else you can do with for loops is nest them. Instead of writing . . .

(for ([i '(1 2 3)])
  (for ([j '(1 2 3)])
    (for ([k '(1 2 3)])
      (displayln (list i j k)))))

. . . Racketeers would write the much more concise

(for* ([i '(1 2 3)]
       [j '(1 2 3)]
       [k '(1 2 3)])
  (displayln (list i j k)))

Every kind of for loop comes with this for* variety: for*/list, for*/fold, and so on. Note that for*/list is equivalent to a nested for loop but with all values collected into one list:

> (for*/list ([i '(1 2 3)]
              [j '(4 5 6)])
   (+ i j))
'(5 6 7 6 7 8 7 8 9)
> (for/list ([i '(1 2 3)])
   (for/list ([j '(4 5 6)])
     (+ i j)))
'((5 6 7) (6 7 8) (7 8 9))

The sublists in a for* are evaluated just as they would be in a nested for loop—every time a level runs, the corresponding list is evaluated. That allows us to compute values like this:

> (for*/list ([k '((1 2) (3 4) (5 6) (7 8))]
              [n k])
    n)
'(1 2 3 4 5 6 7 8)

Here, we flatten a list of lists—turn it into a single list of numbers—by binding each element in the superlist to k, binding each element of those lists to n, and returning those values. We can do this because every time we change elements in the list bound to k, the initial value for n is recomputed.

There is one more piece of for loop magic: in-range. We can use it to iterate across ranges of numbers or to perform an action a set number of times. The in-range function accepts one to three arguments and returns something a for loop can iterate over:

> (for/list ([i (in-range 10)])
    i)
'(0 1 2 3 4 5 6 7 8 9)
> (for/list ([i (in-range 5 10)])
    i)
'(5 6 7 8 9)
> (for/list ([i (in-range 0 10 2)])
    i)
'(0 2 4 6 8)

When given n as an argument, in-range returns a sequence containing the numbers from 0 to n minus 1. Given two arguments, it returns a sequence from the first argument to one less than the second. If it is given a third argument, it increments each element of the sequence by the third argument until it reaches the second argument. Try it out.

Now, in-range is different from all the other Racket functions we have seen before:

> (in-range 10)
#<stream>

It returns a stream, which is a special kind of sequence.

As it turns out, for loops can iterate across many different kinds of sequences—even strings. There is not enough room in this book to cover all of them, but if you’re interested, look up “sequences” in the Racket documentation.

The following table describes a few other loops. There are more; if you’re interested, you know where to look.

Loop

Purpose

Example

Value

for/and

Joins all the results with and.

(for/and ([i '(1 2
f)])  i)

#f

for/or

Joins all the results with or.

(for/or ([i '(1 2
f)])  i)

1

for/first

Like for but it returns the first result.

(for/first ([i '(1 2 3)])
  i)

1

for/last

Like for but it returns the last result.

(for/last ([i '(1 2 3)])
  i)

3

In this chapter, we saw an alternative way of writing loops:

image with no caption
image with no caption