Using Common Lisp Loop Macros

In this chapter, we will discuss several useful macros for performing iteration (we saw how to use recursion for iteration in Chapter 2):

  • dolist–a simple way to process the elements of a list
  • dotimes–a simple way to iterate with an integer valued loop variable
  • do–the most general looping macro
  • loop–a complex looping macro that I almost never use in my own code because it does not look “Lisp like.” I don’t use the loop macro in this book. Many programmers do like the loop macro so you are likely to see it when reading other people’s code.

dolist

We saw a quick example of dolist in the last chapter. The arguments of the dolist macro are:

1    (dolist (a-variable a-list [optional-result-value])   ...body... )

Usually, the dolist macro returns nil as its value, but we can add a third optional argument which will be returned as the generated expression’s value; for example:

1 * (dolist (a '(1 2) 'done) (print a))
2 1 
3 2 
4 DONE
5 * (dolist (a '(1 2)) (print a))
6 1 
7 2 
8 NIL
9 * 

The first argument to the dolist macro is a local lexically scoped variable. Once the code generated by the dolist macro finishes executing, this variable is undefined.

dotimes

The dotimes macro is used when you need a loop with an integer loop index. The arguments of the dotimes macro are:

1    (dotimes (an-index-variable max-index-plus-one [optional-result-value])
2          ...body... )

Usually, the dotimes macro returns nil as its value, but we can add a third optional argument that will be returned as the generated expression’s value; for example:

1 * (dotimes (i 3 "all-done-with-test-dotimes-loop") (print i))
2 
3 0 
4 1 
5 2 
6 "all-done-with-test-dotimes-loop"
7 * 

As with the dolist macro, you will often use a let form inside a dotimes macro to declare additional temporary (lexical) variables.

do

The do macro is more general purpose than either dotimes or dolist but it is more complicated to use. Here is the general form for using the do looping macro:

1   (do ((variable-1 variable-1-init-value variable-1-update-expression)
2           (variable-2 variable-2-init-value variable-2-update-expression)
3           .
4           .
5           (variable-N variable-N-init-value variable-N-update-expression))
6         (loop-termination-test  loop-return-value)
7         optional-variable-declarations
8         expressions-to-be-executed-inside-the-loop)

There is a similar macro do* that is analogous to let* in that loop variable values can depend on the values or previously declared loop variable values.

As a simple example, here is a loop to print out the integers from 0 to 3. This example is in the file src/do1.lisp:

;; example do macro use

1 (do ((i 0 (1+ i)))
2         ((> i 3) "value-of-do-loop")
3   (print i))

In this example, we only declare one loop variable so we might as well as used the simpler dotimes macro.

Here we load the file src/do1.lisp:

1 * (load "do1.lisp")
2 ;; Loading file do1.lisp ...
3 0 
4 1 
5 2 
6 3 
7 ;; Loading of file do1.lisp is finished.
8 T
9 * 

You will notice that we do not see the return value of the do loop (i.e., the string “value-of-do-loop”) because the top-level form that we are evaluating is a call to the function load; we do see the return value of load printed. If we had manually typed this example loop in the Lisp listener, then you would see the final value value-of-do-loop printed.

Using the loop Special Form to Iterate Over Vectors or Arrays

We previousely used dolist to iterate over elements in lists. For efficiency we will often use vectors (one dimensional arrays) and we can use loop to similarly handle vectors:

1   (loop for td across testdata
2      do
3        (print td))))

where testdata is a one dimensional array (a vector) and inside the do block the local variable td is assigned to each element in the vector.