THE LOOP MACRO
In the chapter on Lisp Fundamentals, we briefly touched on the loop macro.
However, with a deeper knowledge of the loop macro, you can replace the use of
several built-in functions with just loop. It also has the advantage of being
easier to understand for those that are familiar with other languages (although
that led it to being somewhat controversial among lispers when it was
introduced).
Given the amount of utility you can get out of a good understanding of loop,
we'll take a closer look at it, comparing to other built-in functions along the way.
Simple Loops
The simplest loop runs forever:
(loop (print "forever")
(return))
This is the "simple" form of loop. It takes a body with no clauses and
repeats it. You'll rarely use this form–it's only useful when combined with
return or return-from to break out manually. The "extended" form, which uses
clauses like :for and :collect, is what the rest of this chapter covers.
Iterating Over Lists
The most basic pattern iterates over the elements of a list:
(loop :for item :in '(one two three)
:do (print item))
Compare this with dolist:
(dolist (item '(one two three))
(print item))
They do the same thing. dolist is shorter for this case. When you need to do
many operations, however, then you'll find loop is more efficient and easier
to understand.
Where :in binds the variable to each element of the list, :on binds it to
each successive sublist:
(loop :for sublist :on '(a b c d)
:do (print sublist))
Iterating Over Vectors and Strings
:in works on lists. For vectors (including strings), use :across:
(loop :for char :across "hello"
:collect (char-upcase char))
(loop :for elem :across #(10 20 30 40)
:sum elem)
Compare with map:
(map 'list #'char-upcase "hello")
Result(#####)
map is more concise here, but loop lets you mix in conditions, multiple
accumulations, and other clauses that map can't express.
Iterating over Hash-Tables
There are special keywords for working with hash-tables as well: :being :the
:hash-keys :of, :being :the :hash-values :of, :using (hash-value value), and
:using (hash-key key).
(defparameter *ht* (make-hash-table))
(setf (gethash 'a *ht*) 10)
(setf (gethash 'b *ht*) 20)
(setf (gethash "foo" *ht*) 30) ; string key
Result30
Iterate over keys only:
(loop :for key :being :the :hash-keys :of *ht*
:do (format t "Key: ~A~%" key))
ResultKey: A
Key: B
Key: foo
=> NIL
Iterate over values only:
(loop :for value :being :the :hash-values :of *ht*
:do (format t "Value: ~A~%" value))
ResultValue: 10
Value: 20
Value: 30
=> NIL
More commonly, you want both keys and values during your loops:
(loop :for key :being :the :hash-keys :of *ht*
:using (hash-value value)
:do (format t "Key: ~A Value: ~A~%" key value))
ResultKey: A Value: 10
Key: B Value: 20
Key: foo Value: 30
=> NIL
Of course, you can start with the values instead:
(loop :for value :being :the :hash-values :of *ht*
:using (hash-key key)
:do (format t "Key: ~A Value: ~A~%" key value))
ResultKey: A Value: 10
Key: B Value: 20
Key: foo Value: 30
=> NIL
Numeric Iteration
You can, of course, iterated over number ranges. You can run the loop body a
fixed number of times with :repeat:
(loop :repeat 3
:do (print "hello"))
If you need an "index" variable, then use :from ... :to ...
(loop :for i :from 0 :to 4
:collect i)
:to is inclusive. If you want exclusive, use :below:
(loop :for i :from 0 :below 4
:collect i)
Compare with dotimes:
(let ((result nil))
(dotimes (i 4 (nreverse result))
(push i result)))
dotimes gets the job done, but loop with :collect saves you from
manually pushing and reversing.
You can count down:
(loop :for i :from 10 :downto 1
:do (format t "~a... " i))
:above is the exclusive version of :downto:
(loop :for i :from 10 :above 0
:collect i)
And you can step by any amount:
(loop :for i :from 0 :to 20 :by 5
:collect i)
Accumulation Clauses
:collect is the most common accumulation clause, but there are several others.
It builds a list from the values:
(loop :for n :in '(1 2 3 4 5)
:collect (* 2 (sqrt n)))
If you need to flattens lists of lists, use :append:
(loop :for sublist :in '((1 2) (3 4) (5 6))
:append sublist)
Compare with reduce:
(reduce #'append '((1 2) (3 4) (5 6)))
:nconc does the same thing destructively (faster, but mutates the sublists).
Use :append unless you're sure the sublists are freshly created and won't be
needed again.
You can accumulate via summing with :sum or counting with :count:
(loop :for n :in '(3 1 4 1 5 9 2 6)
:sum n)
(loop :for n :in '(3 1 4 1 5 9 2 6)
:count (oddp n))
Compare :sum with reduce:
(reduce #'+ '(3 1 4 1 5 9 2 6))
You can get the maximum or minimum values in some collection with :maximize
and :minimize:
(loop :for n :in '(3 1 4 1 5 9 2 6)
:maximize n)
(loop :for n :in '(3 1 4 1 5 9 2 6)
:minimize n)
Compare :count with count-if:
(count-if #'oddp '(3 1 4 1 5 9 2 6))
Conditional Clauses
You can filter data with when and unless clauses:
(loop :for n :in '(1 2 3 4 5 6 7 8 9 10)
:when (evenp n)
:collect n)
Compare with remove-if-not:
(remove-if-not #'evenp '(1 2 3 4 5 6 7 8 9 10))
:unless is the opposite of :when:
(loop :for n :in '(1 2 3 4 5 6 7 8 9 10)
:unless (evenp n)
:collect n)
For more complex branching, use :if, :else, :end, and :finally:
(loop :for n :in '(1 2 3 4 5 6 7 8 9 10)
:if (evenp n)
:collect n :into evens
:else
:collect n :into odds
:end
:finally (return (list :evens evens :odds odds)))
The :end marks where the :if / :else block ends. :into collects into
a named variable instead of the loop's default return value, and :finally
runs code after the loop finishes.
Termination
:while continues looping as long as the condition is true. :until stops as
soon as the condition becomes true:
(loop :for n :in '(2 4 6 7 8 10)
:while (evenp n)
:collect n)
The loop stopped at 7 because it's odd. Note that 8 and 10 are not visited at
all--:while terminates the loop immediately.
This is useful for reading data from a file:
(with-open-file (stream #P"some-file.txt" :direction :input)
(loop :for line = (read-line stream nil nil)
:while line
:collect line))
Here :for line \= assigns the result of (read-line ...) to line each
iteration, and :while line stops when read-line returns nil (end of
file).
You can exit a loop early with return:
(loop :for n :in '(2 4 6 7 8 10)
:when (oddp n)
:do (return n))
:thereis, :always, and :never are shorthand for common patterns:
(loop :for n :in '(2 4 6 8 10)
:always (evenp n))
(loop :for n :in '(2 4 6 7 8 10)
:always (evenp n))
(loop :for n :in '(2 4 6 7 8 10)
:thereis (oddp n))
(loop :for n :in '(2 4 6 8 10)
:never (oddp n))
Compare with the standard functions:
(every #'evenp '(2 4 6 8 10))
(some #'oddp '(2 4 6 7 8 10))
(notany #'oddp '(2 4 6 8 10))
Manually Updated Variable Bindings
:with creates a local variable that persists across iterations but is not a
loop variable. It won't step or update automatically. Here, the total is
manually incremented with incf:
(loop :with total = 0
:for n :in '(1 2 3 4 5)
:do (incf total n)
:finally (return total))
Of course, :sum would be simpler here. :with is more useful when you need
to maintain state that doesn't fit neatly into an accumulation clause:
(loop :with prev = nil
:for n :in '(1 1 2 3 3 3 4 5 5)
:unless (eql n prev)
:collect n
:do (setf prev n))
This removes consecutive duplicates–something that would require a let and
manual bookkeeping with dolist.
Parallel Iterating
You can iterate over multiple things in parallel. The loop ends when the shortest sequence runs out:
(loop :for name :in '("Alice" "Bob" "Charlie")
:for i :from 1
:collect (format nil "~a. ~a" i name))
Result("1. Alice" "2. Bob" "3. Charlie")
Compare with mapcar:
(let ((i 0))
(mapcar (lambda (name)
(incf i)
(format nil "~a. ~a" i name))
'("Alice" "Bob" "Charlie")))
The loop version is cleaner because it has a built-in counter. No need for an
external variable.
You can also iterate over different types of sequences in parallel:
(loop :for letter :across "abcde"
:for number :in '(1 2 3 4 5)
:collect (list letter number))
Result((#1) (#2) (#3) (#4) (#5))
Destructuring
loop can destructure list elements, pulling them apart as it iterates:
(loop :for (name age) :in '(("Alice" 30) ("Bob" 25) ("Charlie" 35))
:collect (format nil "~a is ~a years old" name age))
Result("Alice is 30 years old" "Bob is 25 years old" "Charlie is 35 years old")
This is particularly useful for association lists:
(defparameter *people*
'((:micah male 40 japan)
(:takae female 35 japan)
(:mom female 71 america)))
(loop :for (name gender age country) :in *people*
:when (eql country 'japan)
:collect name)
Result(:MICAH :TAKAE)
Without loop destructuring, you'd need first, second, third, etc.:
(mapcar #'first
(remove-if-not (lambda (row) (eql (fourth row) 'japan))
*people*))
The loop version reads more naturally, especially as the structure gets more
complex.
If you want to step through a plist you can combine :on with :by:
(loop :for (key value) :on '(:name "Micah" :age 40 :lang "Lisp")
:by #'cddr
:do (format t "~&~a: ~a" key value))
ResultNAME: Micah
AGE: 40
LANG: Lisp
=> NIL
:by specifies the stepping function. The default is #'cdr (move one element
forward). Using #'cddr moves two elements forward, which is exactly what you
need for key-value pairs.
The Step Binding
Sometimes you need to compute a value each iteration rather than pull it from a sequence:
(loop :for line = (read-line *standard-input* nil nil)
:while line
:collect line)
This assigns the result of the expression to line each time through. Without
:then, the same expression is evaluated every iteration.
With :then, you can specify different expressions for the first iteration and
subsequent ones:
(loop :for x = 1 :then (* x 2)
:repeat 8
:collect x)
Result(1 2 4 8 16 32 64 128)
Running Code Before The First Iteration Or After The Last Iteration
:initially runs code before the first iteration. :finally runs code after
the last iteration:
(loop :for n :in '(1 2 3 4 5)
:sum n :into total
:finally (return (/ total 5)))
This computes an average. :finally has access to variables created by :into,
which makes it useful for post-processing accumulated results.
(loop :initially (format t "~&Starting...~%")
:for item :in '(a b c)
:do (format t "~&Processing ~a~%" item)
:finally (format t "~&Done.~%"))
ResultStarting...
Processing A
Processing B
Processing C
Done.
=> NIL
Returning From Named Loops
You can name a loop with :named and exit it with return-from. This is
useful for nested loops:
(loop :named outer
:for x :in '(1 2 3)
:do (loop :for y :in '(10 20 30)
:when (and (= x 2) (= y 20))
:do (return-from outer (list x y))))
Result(2 20)
Without :named, a return from the inner loop would only exit the inner
loop. return-from lets you break out of any enclosing named block.
Combining Everything
The real power of loop shows when you combine clauses. Here's a single pass
over a list that does several things at once:
(loop :for n :in '(3 1 4 1 5 9 2 6 5 3 5)
:count (oddp n) :into odd-count
:sum n :into total
:maximize n :into biggest
:when (evenp n)
:collect n :into evens
:finally (return (list :odd-count odd-count
:total total
:biggest biggest
:evens evens)))
Result(:ODD-COUNT 8 :TOTAL 44 :BIGGEST 9 :EVENS (4 2 6))
Doing this without loop would require a let with four variables and a
dolist with manual bookkeeping for each accumulation. The loop version
declares what you want and lets the macro handle the mechanics.
When to Use Something Else
loop is not always the right tool.
For a simple transformation of each element, mapcar is more concise:
(mapcar #'1+ '(1 2 3))
(loop :for n :in '(1 2 3) :collect (1+ n))
For a simple predicate test, every, some, notany are clearer:
(every #'evenp numbers)
(loop :for n :in numbers :always (evenp n))
For reducing to a single value with a known function, reduce says it directly:
(reduce #'+ numbers)
(loop :for n :in numbers :sum n)
For a simple side effect on each element, dolist is fine:
(dolist (item items) (print item))
(loop :for item :in items :do (print item))
Use loop when the iteration is complex enough that you'd otherwise need to
combine multiple constructs, maintain manual state, or make multiple passes over
the data. If a simpler construct expresses your intent clearly, prefer it.
A Note on Style: Keywords vs Symbols
You'll see loop written two ways:
;; With keywords (this book's style):
(loop :for i :from 0 :below 5 :collect i)
;; With plain symbols:
(loop for i from 0 below 5 collect i)
Both are valid. The keyword style (with colons) makes the loop clauses visually
distinct from variables and expressions. This helps make inner variables (like
i above) distinct and easy to find. Prefer the keyword style.

