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.
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?
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.
Waitpid
—Chapter CheckpointIn this chapter, we saw an alternative way of writing loops:
There are several variants of for
loops. Each combines its results in a different way.
The for*
loop is convenient shorthand for writing nested loops.
A for
loop iterates over a sequence of values. You can make a sequence using lists, strings, in-range
, in-list
, vectors, and many other values.