Background
Every once in a while, I see a mention of how Common Lisp really does support “non-functional” styles, and then cl:loop
is trotted in, with attendant examples that make your eyes glaze over.
As you might expect, this isn’t the only way to do it; there’s a “Lispier” way, if you will (none of this is new, the original manual/memo dates from 1989!).
Installing
CL-USER> (ql:quickload "iterate") To load "iterate": Load 1 ASDF system: asdf Install 1 Quicklisp release: iterate ; Loading "iterate" [package iterate]........................... ("iterate")
(while I’m here, a plug for sly
instead of slime
; you know you’re successfully connected when you see the [sly] Connected. Take this REPL, brother, and may it serve you well.
message 😀)
Simple uses
For a really simple example, iter
is not too different from loop
, but still:
Basic for loop
CL-USER> (loop for i from 1 to 10 collect i) (1 2 3 4 5 6 7 8 9 10)
CL-USER> (iter:iter (for i from 1 to 10) (collect i)) (1 2 3 4 5 6 7 8 9 10)
Collecting tuples
CL-USER> (loop for x in '(a b c d) for y in '(d e f g) collect (list x y)) ((A D) (B E) (C F) (D G))
CL-USER> (iter:iter (for x in '(a b c d)) (for y in '(d e f g)) (collect (list x y))) ((A D) (B E) (C F) (D G))
Intermediate example
Here is an example (from the CL cookbook) of looping, with an auxiliary variable on which we have a terminating condition, with a combination of “doing something” and collecting something else, at the same time:
CL-USER> (loop for x from 1 for y = (* x 10) while (< y 100) do (print (* x 5)) collect y) 5 10 15 20 25 30 35 40 45 (10 20 30 40 50 60 70 80 90)
CL-USER> (iter:iter (for x upfrom 1) (for y = (* x 10)) (while (< y 100)) (print (* x 5)) (collect y)) 5 10 15 20 25 30 35 40 45 (10 20 30 40 50 60 70 80 90)
Another example, though a bit contrived (there’s a one-liner to do this without using either of these two, but …)
CL-USER> (let ((s "alpha45")) (loop for i from 0 below (length s) for ch = (char s i) when (find ch "0123456789" :test #'eql) return ch) ) #\4
CL-USER> (let ((s "alpha45")) (iter:iter (for ch in-string s) (finding ch such-that (find ch "0123456789" :test #'eql)))) #\4
Misc cool stuff
Making modifications
I find it easier to “splice in” new changes to iter
. This is another contrived example, but sort of shows what I mean:
CL-USER> (iter:iter (for i from 1 to 10) (collect i into nums) (finally (return nums))) (1 2 3 4 5 6 7 8 9 10)
CL-USER> (iter:iter (for i from 1 to 10) (collect i into nums) (collect (* i i) into nums) (finally (return nums))) (1 1 2 4 3 9 4 16 5 25 6 36 7 49 8 64 9 81 10 100)
Natural iteration for different types
The (for ... in ...)
gathering clause applies quite naturally to a great many types of structures.
CL-USER> (iter:iter (for x in '(1 5 6)) (when (oddp x) (collect x))) (1 5)
CL-USER> (iter:iter (for x in-vector #(1 5 6)) (when (oddp x) (collect x))) (1 5)
CL-USER> (iter:iter (for (k v) in-hashtable (alexandria:alist-hash-table '((foo bar) (baz quux)))) (collect v)) ((BAR) (QUUX))
Accessing previous values
CL-USER> (iter:iter (for x from 1 to 10) (for p-x previous x initially 0) (collect (+ x p-x))) (1 3 5 7 9 11 13 15 17 19)
Collecting all vs. unique values
CL-USER> (iter:iter (for x in '(7 1 4 3 2 1 7 1 0)) (collect x)) (7 1 4 3 2 1 7 1 0)
CL-USER> (iter:iter (for x in '(7 1 4 3 2 1 7 1 0)) (adjoining x)) (7 1 4 3 2 0)
Reductions
You can splice in a reduction step (counting, summing, maximizing, minimizing, etc.) in ad-hoc way.
This extremely contrived example is essentially equivalent to (reduce #'* '(1 2 3 4 5 6 7 8 9 10))
, but hopefully you get the point:
CL-USER> (iter:iter (for x from 1 to 10) (reducing x by #'* )) 3628800 (22 bits, #x375F00)
Conclusion
YMMV, but iter
seems to have (for me) a more uniform syntax, a few extra features, better comparability of clauses, and I personally prefer it to loop
. If you’ve never used either, I’d recommend just sticking with the former.