Common Lisp nits/tips/libraries: Iteration

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.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s