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.