The Common Lisp Cookbook – Loop, iteration, mapping

Table of Contents

The Common Lisp Cookbook – Loop, iteration, mapping

šŸ“¢ New videos: web dev demo part 1, dynamic page with HTMX, Weblocks demo

šŸ“• Get the EPUB and PDF

Introduction: loop, iterate, for, mapcar, series, transducers

The loop macro

loop is the built-in macro for iteration.

Its simplest form is (loop (print "hello")): this will print forever.

A simple iteration over a list is:

(loop for x in '(1 2 3)
  do (print x))

It prints whatā€™s needed but returns nil.

If you want to return a list, use collect:

(loop for x in '(1 2 3)
  collect (* x 10))
;; (10 20 30)

The Loop macro is different than most Lisp expressions in having a complex internal domain-specific language that doesnā€™t use s-expressions. So you need to read Loop expressions with half of your brain in Lisp mode, and the other half in Loop mode. You love it or you hate it.

Think of Loop expressions as having four parts: expressions that set up variables that will be iterated, expressions that conditionally terminate the iteration, expressions that do something on each iteration, and expressions that do something right before the Loop exits. In addition, Loop expressions can return a value. It is very rare to use all of these parts in a given Loop expression, but you can combine them in many ways.

The iterate macro

iterate is a popular iteration macro that aims at being simpler, ā€œlispierā€ and more predictable than loop, besides being extensible. However it isnā€™t built-in, so you have to import it:

(ql:quickload "iterate")
(use-package :iterate)

Iterate looks like this:

(iter (for i from 1 to 5)
    (collect (* i i)))

(if you use loop and iterate in the same package, you might run into name conflicts)

Iterate also comes with display-iterate-clauses that can be quite handy:

(display-iterate-clauses '(for))
;; FOR PREVIOUS &OPTIONAL INITIALLY BACK     Previous value of a variable
;; FOR FIRST THEN            Set var on first, and then on subsequent iterations
;; ...

Much of the examples on this page that are valid for loop are also valid for iterate, with minor modifications.

The for macro

for is an extensible iteration macro that is often shorter than loop, that ā€œunlike loop is extensible and sensible, and unlike iterate does not require code-walking and is easier to extendā€.

It has the other advantage of having one construct that works for all data structures (lists, vectors, hash-tablesā€¦): in doubt, just use forā€¦ over:

(for:for ((x over <your data structure>))
   (print ā€¦))

You also have to quickload it:

(ql:quickload "for")

Weā€™ll also give examples with mapcar and map, and eventually with their friends mapcon, mapcan, maplist, mapc and mapl which E. Weitz categorizes very well in his ā€œCommon Lisp Recipesā€, chap. 7. The one you are certainly accustomed to from other languages is mapcar: it takes a function, one or more lists as arguments, applies the function on each element of the lists one by one and returns a list of result.

(mapcar (lambda (it) (+ it 10)) '(1 2 3))
(11 12 13)

map is generic, it accepts list and vectors as arguments, and expects the type for its result as first argument:

(map 'vector (lambda (it) (+ it 10)) '(1 2 3))
;; #(11 12 13)
(map 'list (lambda (it) (+ it 10)) #(1 2 3))
;; (11 12 13)
(map 'string (lambda (it) (code-char it)) '#(97 98 99))
;; "abc"

The other constructs have their advantages in some situations ;) They either process the tails of lists, or concatenate the return values, or donā€™t return anything. Weā€™ll see some of them.

If you like mapcar, use it a lot, and would like a quicker and shorter way to write lambdas, then you might like one of those lambda shorthand libraries.

Here is an example with cl-punch:

(mapcar ^(* _ 10) '(1 2 3))
;; (10 20 30)

and voilĆ  :) We wonā€™t use this more in this recipe, but feel free to do.

The series library

You might also like series, a library that describes itself as combining aspects of sequences, streams, and loops. Series expressions look like operations on sequences (= functional programming), but can achieve the same high level of efficiency as a loop. Series first appeared in ā€œCommon Lisp the Languageā€, in the appendix A (it nearly became part of the language). Series looks like this:

(collect
  (mapping ((x (scan-range :from 1 :upto 5)))
    (* x x)))
;; (1 4 9 16 25)

series is good, but its function names are different from what we find in functional languages today. You might like the ā€œGenerators The Way I Want Them Generatedā€ library. It is a lazy sequences library, similar to series although younger and not as complete, with a ā€œmodernā€ API with words like take, filter, for or fold, and that is easy to use.

(range :from 20)
;; #<GTWIWTG::GENERATOR! {1001A90CA3}>

(take 4 (range :from 20))
;; (20 21 22 23)

At the time of writing, GTWIWTG is licensed under the GPLv3.

The transducers library

The transducers pattern was ported to Common Lisp in 2023 and offers a full suite of functional programming idioms for efficiently iterating over ā€œsourcesā€. A ā€œsourceā€ could be simple collections like Lists or Vectors, but also potentially large files or generators of infinite data.

Transducersā€¦

Letā€™s sum the squares of the first 1000 odd integers:

(defpackage foo
  (:use :cl)
  (:local-nicknames (:t :transducers)))

(t:transduce
 (t:comp (t:filter #'oddp)             ;; (2) Keep only odd numbers.
         (t:take 1000)                 ;; (3) Keep the first 1000 filtered odds.
         (t:map (lambda (n) (* n n)))) ;; (4) Square those 1000.
 #'+         ;; (5) Reducer: Add up all the squares.
 (t:ints 1)) ;; (1) Source: Generate all positive integers.
;; => 1333333000 (31 bits, #x4F790C08)

Here, even though ints is an infinite generator, only as many values as are needed for the final result are actually created.

The user is free to invent their own transducers (i.e. functions like map) and reducers (i.e. functions like +) to traverse data streams in any way they wish, all while being very memory efficient.

See its README, its API, or the original Transducers document for more information.

Recipes

Looping forever, return

(loop
    (print "hello"))

return can return a result:

(loop for i in '(1 2 3)
     when (> i 1)
     return i)
2

Looping a fixed number of times

dotimes

(dotimes (n 3)
  (print n))
;; =>
;; 0
;; 1
;; 2
;; NIL

Here dotimes returns nil. There are two ways to return a value. First, you can set a result form in the lambda list:

(dotimes (n 3 :done)
  ;;          ^^^^^ result form. It can be a s-expression.
  (print n))
;; =>
;; 0
;; 1
;; 2
;; :DONE

Or you can use return with return values:

(dotimes (i 3)
   (if (> i 1)
       (return :early-exit!)
       (print i)))
;; =>
;; 0
;; 1
;; :EARLY-EXIT!

loopā€¦ repeat

(loop repeat 10
  do (format t "Hello!~%"))

This prints 10 times ā€œhelloā€ and returns nil.

(loop repeat 10 collect (random 10))
;; (5 1 3 5 4 0 7 4 9 1)

with collect, this returns a list.

Series

(iterate ((n (scan-range :below 10)))
  (print n))

Looping an infinite number of times, cycling over a circular list

First, as shown above, we can simply use (loop ...) to loop infinitely. Here we show how to loop on a list forever.

We can build an infinite list by setting its last element to the list itself:

(loop with list-a = (list 1 2 3)
      with infinite-list = (setf (cdr (last list-a)) list-a)
      for item in infinite-list
      repeat 8
      collect item)
;; (1 2 3 1 2 3 1 2)

Illustration: (last (list 1 2 3)) is (3), a list, or rather a cons cell, whose car is 3 and cdr is NIL. See the data-structures chapter for a reminder. This is the representation of (list 3):

[o|/]
 |
 3

The representation of (list 1 2 3):

[o|o]---[o|o]---[o|/]
 |       |       |
 1       2       3

By setting the cdr of the last element to the list itself, we make it recur on itself.

A notation shortcut is possible with the #= syntax:

(defparameter *list-a* '#1=(1 2 3 . #1#))
(setf *print-circle* t)  ;; don't print circular lists forever
*list-a*

If you need to alternate only between two values, use for ā€¦ then:

(loop repeat 4
      for up = t then (not up)
      do (print up))
T
NIL
T
NIL

Iterateā€™s for loop

For lists and vectors:

(iter (for item in '(1 2 3))
  (print item))
(iter (for i in-vector #(1 2 3))
  (print i))

or, a generalized iteration clause for lists and vectors, use in-sequence (youā€™ll pay a speed penalty).

Looping over a hash-table is also straightforward:

(let ((h (let ((h (make-hash-table)))
           (setf (gethash 'a h) 1)
           (setf (gethash 'b h) 2)
           h)))
  (iter (for (k v) in-hashtable h)
    (print k)))
;; b
;; a

In fact, take a look here, or (display-iterate-clauses '(for)) to know about iterating over

Looping over a list

dolist

(dolist (item '(1 2 3))
  (print item))

dolist returns nil.

loop

with in, no surprises:

(loop for x in '(a b c)
      do (print x))
;; A
;; B
;; C
;; NIL
(loop for x in '(a b c)
      collect x)
;; (A B C)

With on, we loop over the cdr of the list:

(loop for i on '(1 2 3) do (print i))
;; (1 2 3)
;; (2 3)
;; (3)

mapcar

(mapcar (lambda (x)
             (print (* x 10)))
         '(1 2 3))
10
20
30
(10 20 30)

mapcar returns the results of the lambda function as a list.

Series

(iterate ((item (scan '(1 2 3))))
  (print item))

scan-sublists is the equivalent of loop for ... on:

(iterate ((i (scan-sublists '(1 2 3))))
  (print i))

Looping over a vector

loop: across

(loop for i across #(1 2 3) do (print i))

Series

(iterate ((i (scan #(1 2 3))))
  (print i))

Looping over a hash-table

We create a hash-table:

(defparameter h (make-hash-table))
(setf (gethash 'a h) 1)
(setf (gethash 'b h) 2)

Looping over keys and values

Looping over keys:

(loop for k being the hash-key of h do (print k))
;; b
;; a

Looping over values uses the same concept but with the hash-value keyword instead of hash-key:

(loop for k being the hash-value of h do (print k))
;; 1
;; 2

Looping over key-values pairs:

(loop for k
    being the hash-key
    using (hash-value v) of h
    do (format t "~a ~a~%" k v))
b 2
a 1

iterate

Use in-hashtable:

(iter (for (key value) in-hashtable h)
  (collect (list key value)))

for

the same with for:

(for:for ((it over h))
    (print it))
(A 1)
(B 2)
NIL

maphash

The lambda function of maphash takes two arguments: the key and the value:

(maphash (lambda (key val)
             (format t "key: ~a val:~a~&" key val))
          h)
;; key: A val:1
;; key: B val:2
;; NIL

See also with-hash-table-iterator.

dohash

Only because we like this topic, we introduce another library, trivial-do. It has the dohash macro, that ressembles dolist:

(dohash (key value h)
  (format t "key: ~A, value: ~A~%" key value))

Series

(iterate (((k v) (scan-hash h)))
  (format t "~&~a ~a~%" k v))

Looping over two lists in parallel

loop

(loop for x in '(a b c)
      for y in '(1 2 3)
      collect (list x y))
;; ((A 1) (B 2) (C 3))

To return a flat list, use nconcing instead of collect:

(loop for x in '(a b c)
      for y in '(1 2 3)
      nconcing (list x y))
(A 1 B 2 C 3)

If a list is smaller than the other one, loop stops at the end of the small one:

(loop for x in '(a b c)
      for y in '(1 2 3 4 5)
      collect (list x y))
;; ((A 1) (B 2) (C 3))

We could loop over the biggest list and manually access the elements of the smaller one by index, but it would quickly be inefficient. Instead, we can tell loop to extend the short list.

(loop for y in '(1 2 3 4 5)
      for x-list = '(a b c) then (cdr x-list)
      for x = (or (car x-list) 'z)
      collect (list x y))
;; ((A 1) (B 2) (C 3) (Z 4) (Z 5))

The trick is that the notation for ā€¦ = ā€¦ then (cdr ā€¦) (note the = and the role of then) shortens our intermediate list at each iteration (thanks to cdr). It will first be '(a b c), the initial value, then we will get the cdr: (2 3), then (3), then NIL. And both (car NIL) and (cdr NIL) return NIL, so we are good.

mapcar

(mapcar (lambda (x y)
          (list x y))
        '(a b c)
        '(1 2 3))
;; ((A 1) (B 2) (C 3))

or simply:

(mapcar #'list
        '(a b c)
        '(1 2 3))
;; ((A 1) (B 2) (C 3))

Return a flat list:

(mapcan (lambda (x y)
          (list x y))
        '(a b c)
        '(1 2 3))
;; (A 1 B 2 C 3)

Series

(collect
  (#Mlist (scan '(a b c))
          (scan '(1 2 3))))

A more efficient way, when the lists are known to be of equal length:

(collect
  (mapping (((x y) (scan-multiple 'list
                                  '(a b c)
                                  '(1 2 3))))
    (list x y)))

Return a flat list:

(collect-append ; or collect-nconc
 (mapping (((x y) (scan-multiple 'list
                                 '(a b c)
                                 '(1 2 3))))
   (list x y)))

Nested loops

loop

(loop for x from 1 to 3
      collect (loop for y from 1 to x
		    collect y))
;; ((1) (1 2) (1 2 3))

To return a flat list, use nconcing instead of the first collect.

iterate

(iter outer
   (for i below 2)
   (iter (for j below 3)
      (in outer (collect (list i j)))))
;; ((0 0) (0 1) (0 2) (1 0) (1 1) (1 2))

Series

(collect
  (mapping ((x (scan-range :from 1 :upto 3)))
    (collect (scan-range :from 1 :upto x))))

Computing an intermediate value

Use =.

With for:

(loop for x from 1 to 3
      for y = (* x 10)
      collect y)
;; (10 20 30)

With with, the difference being that the value is computed only once:

(loop for x from 1 to 3
      for y = (* x 10)
      with z = x
      collect (list x y z))
;; ((1 10 1) (2 20 1) (3 30 1))

The HyperSpec defines the with clause like this:

with-clause::= with var1 [type-spec] [= form1] {and var2 [type-spec] [= form2]}*

so it turns out we can specify the type before the = and chain the with with and:

(loop for x from 1 to 3
      for y integer = (* x 10)
      with z integer = x
      collect (list x y z))
(loop for x upto 3
      with foo = :foo
      and bar = :bar
      collect (list x foo bar))

We can also give for a then clause that will be called at each iteration:

(loop repeat 3
      for intermediate = 10 then (incf intermediate)
      do (print intermediate))
10
11
12

Hereā€™s a trick to alternate a boolean:

(loop repeat 4
      for up = t then (not up)
      do (print up))

T
NIL
T
NIL

Loop with a counter

loop

Iterate through a list, and have a counter iterate in parallel. The length of the list determines when the iteration ends. Two sets of actions are defined, one of which is executed conditionally.

* (loop for x in '(a b c d e)
      for y from 1

      when (> y 1)
      do (format t ", ")

      do (format t "~A" x)
      )

A, B, C, D, E
NIL

We could also write the preceding loop using the IF construct.

* (loop for x in '(a b c d e)
      for y from 1

      if (> y 1)
      do (format t ", ~A" x)
      else do (format t "~A" x)
      )

A, B, C, D, E
NIL

Series

By iterating on multiple series in parallel, and using an infinite range, we can make a counter.

(iterate ((x (scan '(a b c d e)))
          (y (scan-range :from 1)))
  (when (> y 1) (format t ", "))
  (format t "~A" x))

Ascending, descending order, limits

loop

fromā€¦ toā€¦:

(loop for i from 0 to 10
      do (print i))
;; 0 1 2 3 4 5 6 7 8 9 10

fromā€¦ belowā€¦: this stops at 9:

(loop for i from 0 below 10
      do (print i))

Similarly, use from 10 downto 0 (10ā€¦0) and from 10 above 0 (10ā€¦1).

Series

:from ... :upto, including the upper limit:

(iterate ((i (scan-range :from 0 :upto 10)))
  (print i))

:from ... :below, excluding the upper limit:

(iterate ((i (scan-range :from 0 :below 10)))
  (print i))

Steps

loop

with by:

(loop for i from 1 to 10 by 2
      do (print i))

if you use by (1+ (random 3)), the random is evaluated only once, as if it was in a closure:

(let ((step (random 3)))
   (loop for i from 1 to 10 by (+ 1 step)
      do (print i)))

The step must always be a positive number. If you want to count down, see above.

Series

with :by:

(iterate ((i (scan-range :from 1 :upto 10 :by 2)))
  (print i))

Loop and conditionals

loop

with if, else and finally:

(loop repeat 10
      for x = (random 100)
      if (evenp x)
        collect x into evens
      else
        collect x into odds
      finally (return (values evens odds)))
(42 82 24 92 92)
(55 89 59 13 49)

Combining multiple clauses in an if body requires special syntax (and do, and count):

 (loop repeat 10
       for x = (random 100)
       if (evenp x)
          collect x into evens
          and do (format t "~a is even!~%" x)
       else
          collect x into odds
          and count t into n-odds
       finally (return (values evens odds n-odds)))
46 is even!
8 is even!
76 is even!
58 is even!
0 is even!
(46 8 76 58 0)
(7 45 43 15 69)
5

iterate

Translating (or even writing!) the above example using iterate is straight-forward:

(iter (repeat 10)
   (for x = (random 100))
   (if (evenp x)
       (progn
         (collect x into evens)
         (format t "~a is even!~%" x))
       (progn
         (collect x into odds)
         (count t into n-odds)))
   (finally (return (values evens odds n-odds))))

Series

The preceding loop would be done a bit differently in Series. split sorts one series into multiple according to provided boolean series.

(let* ((number (#M(lambda (n) (random 100))
                  (scan-range :below 10)))
       (parity (#Mevenp number)))
  (iterate ((n number) (p parity))
    (when p (format t "~a is even!~%" n)))
  (multiple-value-bind (evens odds) (split number parity)
    (values (collect evens)
            (collect odds)
            (collect-length odds))))

Note that although iterate and the three collect expressions are written sequentially, only one iteration is performed, the same as the example with loop.

Begin the loop with a clause (initially)

(loop initially
      (format t "~a " 'loop-begin)
      for x below 3
      do (format t "~a " x))
;; LOOP-BEGIN 0 1 2

initially also exists with iterate.

Terminate the loop with a test (until, while)

loop

(loop for x in '(1 2 3 4 5)
	until (> x 3)
	collect x)
;; (1 2 3)

the same, with while:

(loop for x in '(1 2 3 4 5)
	while (< x 4)
	collect x)

Series

We truncate the series with until-if, then collect from its result.

(collect
  (until-if (lambda (i) (> i 3))
            (scan '(1 2 3 4 5))))

Loop, print and return a result

loop

do and collect can be combined in one expression

(loop for x in '(1 2 3 4 5)
	while (< x 4)
        do (format t "x is ~a~&" x)
	collect x)
x is 1
x is 2
x is 3
(1 2 3)

Series

By mapping, we can perform a side effect and also collect items

(collect
  (mapping ((x (until-if (complement (lambda (x) (< x 4)))
                         (scan '(1 2 3 4 5)))))
    (format t "x is ~a~&" x)
    x))

Named loops and early exit

loop

The special loop named foo syntax allows you to create a loop that you can exit early from. The exit is performed using return-from, and can be used from within nested loops.

;; useless example
(loop named loop-1
    for x from 0 to 10 by 2
    do (loop for y from 0 to 100 by (1+ (random 3))
            when (< x y)
            do (return-from loop-1 (values x y))))
0
2

Sometimes, you want to return early but execute the finally clause anyways. Use loop-finish.

(loop for x from 0 to 100
  do (print x)
  when (>= x 3)
  return x
  finally (print :done))  ;; <-- not printed
;; 0
;; 1
;; 2
;; 3
;; 3

(loop for x from 0 to 100
  do (print x)
  when (>= x 3)
  do (loop-finish)
  finally (print :done)
     (return x))
;; 0
;; 1
;; 2
;; 3
;; :DONE
;; 3

It is most needed when some computation must take place in the finally clause.

Loop shorthands for when/return

Several actions provide shorthands for combinations of when/return:

* (loop for x in '(foo 2)
      thereis (numberp x))
T
* (loop for x in '(foo 2)
      never (numberp x))
NIL
* (loop for x in '(foo 2)
      always (numberp x))
NIL

They correspond to the functions some, notany and every:

(some #'numberp '(foo 2))
(notany #'numberp '(foo 2))
(every #'numberp '(foo 2))

Series

A block is manually created and returned from.

(block loop-1
  (iterate ((x (scan-range :from 0 :upto 10 :by 2)))
    (iterate ((y (scan-range :from 0 :upto 100 :by (1+ (random 3)))))
      (when (< x y)
        (return-from loop-1 (values x y))))))

Count

loop

(loop for i from 1 to 3 count (oddp i))
;; 2

Series

(collect-length (choose-if #'oddp (scan-range :from 1 :upto 3)))

Summation

loop

(loop for i from 1 to 3 sum (* i i))
;; 14

Summing into a variable:

(loop for i from 1 to 3
   sum (* i i) into total
   do (print i)
   finally (print total))
1
2
3
14

Series

(collect-sum (#M(lambda (i) (* i i))
                (scan-range :from 1 :upto 3)))

max, min

loop

(loop for i from 1 to 3 maximize (mod i 3))
;; 2

and minimize.

Series

(collect-max (#M(lambda (i) (mod i 3))
                (scan-range :from 1 :upto 3)))

and collect-min.

Destructuring, aka pattern matching against the list or dotted pairs

loop

(loop for (a b) in '((x 1) (y 2) (z 3))
      collect (list b a) )
;; ((1 X) (2 Y) (3 Z))
(loop for (x . y) in '((1 . a) (2 . b) (3 . c)) collect y)
;; (A B C)

Use nil to ignore a term:

(loop for (a nil) in '((x 1) (y 2) (z 3))
      collect a )
;; (X Y Z)
Iterating over a plist or 2 by 2 over a list

To iterate over a list, 2 items at a time we use a combination of on, by and destructuring.

We use on to loop over the rest (the cdr) of the list.

(loop for rest on '(a 2 b 2 c 3)
      collect rest)
;; ((A 2 B 2 C 3) (2 B 2 C 3) (B 2 C 3) (2 C 3) (C 3) (3))

We use by to skip one element at every iteration ((cddr list) is equivalent to (rest (rest list)))

(loop for rest on '(a 2 b 2 c 3) by #'cddr
      collect rest)
;; ((A 2 B 2 C 3) (B 2 C 3) (C 3))

Then we add destructuring to bind only the first two items at each iteration:

(loop for (key value) on '(a 2 b 2 c 3) by #'cddr
      collect (list key (* 2 value)))
;; ((A 2) (B 4) (C 6))

Series

In general, with destructuring-bind:

(collect
  (mapping ((l (scan '((x 1) (y 2) (z 3)))))
    (destructuring-bind (a b) l
      (list b a))))

But for alists, scan-alist is provided:

(collect
  (mapping (((a b) (scan-alist '((1 . a) (2 . b) (3 . c)))))
    b))

Iterate unique features lacking in loop

iterate has some other things unique to it.

If you are a newcomer in Lisp, itā€™s perfectly OK to keep this section for later. You could very well spend your career in Lisp without resorting to those featuresā€¦ although they might turn out useful one day.

No rigid order for clauses

loop requires that all for clauses appear before the loop body, for example before a while. Itā€™s ok for iter to not follow this order:

(iter (for x in '(1 2 99)
  (while (< x 10))
  (for y = (print x))
  (collect (list x y)))

Accumulating clauses can be nested

collect, appending and other accumulating clauses can appear anywhere:

(iter (for x in '(1 2 3))
  (case x
    (1 (collect :a))
    ;;  ^^ iter keyword, nested in a s-expression.
    (2 (collect :b))))

Finders: finding

iterate has finders.

A finder is a clause whose value is an expression that meets some condition.

We can use finding followed by maximizing, minimizing or such-that.

Hereā€™s how to find the longest list in a list of lists:

(iter (for elt in '((a) (b c d) (e f)))
      (finding elt maximizing (length elt)))
=> (B C D)

The rough equivalent in LOOP would be:

(loop with max-elt = nil
      with max-key = 0
      for elt in '((a) (b c d) (e f))
      for key = (length elt)
      do
      (when (> key max-key)
        (setf max-elt elt
              max-key key))
      finally (return max-elt))
=> (B C D)

There could be more than one such-that clause:

 (iter (for i in '(7 -4 2 -3))
       (if (plusp i)
    (finding i such-that (evenp i))
        (finding (- i) such-that (oddp i))))
;; => 2

We can also write such-that #'evenp and such-that #'oddp.

Control flow: next-iteration

It is like ā€œcontinueā€ and loop doesnā€™t have it.

Skips the remainder of the loop body and begins the next iteration of the loop.

iterate also has first-iteration-p and (if-first-time then else).

See control flow.

Generators

Use generate and next. A generator is lazy, it goes to the next value when said explicitly.

(iter (for i in '(1 2 3 4 5))
      (generate c in-string "black")
      (if (oddp i) (next c))
      (format t "~a " c))
;; b b l l a
;; NIL

Variable backtracking (previous) VS parallel binding

iterate allows us to get the previous value of a variable:

(iter (for el in '(a b c d e))
      (for prev-el previous el)
      (collect (list el prev-el)))
;; => ((A NIL) (B A) (C B) (D C) (E D))

In this case however we can do it with loopā€™s parallel binding and, which is unsupported in iterate:

(loop for el in '(a b c d e)
      and prev-el = nil then el
      collect (list el prev-el))

More clauses

(iter (for c in-string "hello")
      (collect c))
;; => (#\h #\e #\l #\l #\o)
(iter (for el in '(a b c a d b))
      (adjoining el))
;; => (A B C D)

(adjoin is a set operation)

(iter (with dividend = 100)
      (for divisor in '(10 5 2))
      (reducing divisor by #'/ initial-value dividend))
;; => 1

Iterate is extensible

(defmacro dividing-by (num &keys (initial-value 0))
  `(reducing ,num by #'/ initial-value ,initial-value))

(iter (for i in '(10 5 2))
      (dividing-by i :initial-value 100))
=> 1

but there is more to it, see the documentation.

We saw libraries extending loop, for example CLSQL, but they are full of feature flag checks (#+(or allegro clisp-aloop cmu openmcl sbcl scl)) and they call internal modules (ansi-loop::add-loop-path, sb-loop::add-loop-path etc).

Custom series scanners

If we often scan the same type of object, we can write our own scanner for it: the iteration itself can be factored out. Taking the example above, of scanning a list of two-element lists, weā€™ll write a scanner that returns a series of the first elements and a series of the second.

(defun scan-listlist (listlist)
  (declare (optimizable-series-function 2))
  (map-fn '(values t t)
          (lambda (l)
            (destructuring-bind (a b) l
              (values a b)))
          (scan listlist)))

(collect
  (mapping (((a b) (scan-listlist '((x 1) (y 2) (z 3)))))
    (list b a)))

Shorter series expressions

Consider this series expression:

(collect-sum (mapping ((i (scan-range :length 5)))
                    (* i 2)))

Itā€™s a bit longer than it needs to be, the mapping formā€™s only purpose is to bind the variable i, and i is used in only one place. Series has a ā€œhidden featureā€ that allows us to simplify this expression to the following:

(collect-sum (* 2 (scan-range :length 5)))

This is called implicit mapping and can be enabled in the call to series::install:

(series::install :implicit-map t)

When using implicit mapping, the #M reader macro demonstrated above becomes redundant.

Loop gotchas

Iterate gotchas

It breaks on the function count:

(iter (for i from 1 to 10)
      (sum (count i '(1 3 5))))

It doesnā€™t recognize the built-in count function and instead signals a condition.

It works in loop:

(loop for i from 1 to 10
    sum (count i '(1 3 5 99)))
;; 3

Appendix: list of loop keywords

Name Clause

named

Variable Clauses

initially finally for as with

Main Clauses

do collect collecting append
appending nconc nconcing into count
counting sum summing maximize return loop-finish
maximizing minimize minimizing doing
thereis always never if when
unless repeat while until

These donā€™t introduce clauses:

= and it else end from upfrom
above below to upto downto downfrom
in on then across being each the hash-key
hash-keys of using hash-value hash-values
symbol symbols present-symbol
present-symbols external-symbol
external-symbols fixnum float t nil of-type

But note that itā€™s the parsing that determines what is a keyword. For example in:

(loop for key in hash-values)

Only for and in are keywords.

Ā©Dan Robertson on Stack Overflow.

Credit and references

Loop

Iterate

Series

Others

Page source: iteration.md

T
O
C