This post is a recent addition on the Common Lisp Cookbook. Check it out and send updates there!

If you’re afraid of loop, go learn it by example there (you’ll see also examples with Series, mapcar and friends and other little libraries). There are a ton of them and it tremendously helps^^

I didn’t switch to iterate myself (I learned loop by example), but I was still wondering if it had unique features to it (in addition to the lispy syntax, a better hash-table iteration syntax etc). Here’s my best summary.

I relied on these:

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 you 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. They help us find elements that satisfy one or more conditions.

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)

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

The rough equivalent of the above 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

That’s kind of cool.

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

  • in-string can be used explicitly to iterate character by character over a string. With loop, use across.
(iter (for c in-string "hello")
      (collect c))
;; => (#\h #\e #\l #\l #\o)
  • loop offers collecting, nconcing, and appending. iterate has these and also adjoining, unioning, nunioning, and accumulating.
(iter (for el in '(a b c a d b))
      (adjoining el))
;; => (A B C D)

(adjoin is a set operation)

  • loop has summing, counting, maximizing, and minimizing. iterate also includes multiplying and reducing. reducing is the generalized reduction builder:
(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).