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:
- the iterate manual itself: https://common-lisp.net/project/iterate/doc/index.html (by Jonathan Amsterdam and Luís Oliveira), especially their comparision with loop, but it isn’t complete and it lacks code snippets.
- Loop v Iterate by SabraOnTheHill (on its old Google site)
- Comparing loop and iterate, by Stephen Bach (on the web archive)
- and, still, Common Lisp Recipes by Edmund Weitz.
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-stringcan be used explicitly to iterate character by character over a string. With loop, useacross.
(iter (for c in-string "hello")
(collect c))
;; => (#\h #\e #\l #\l #\o)
loopofferscollecting,nconcing, andappending.iteratehas these and alsoadjoining,unioning,nunioning, andaccumulating.
(iter (for el in '(a b c a d b))
(adjoining el))
;; => (A B C D)
(adjoin is a set operation)
loophassumming,counting,maximizing, andminimizing.iteratealso includesmultiplyingandreducing. 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).