Common Lisp loop

August 25, 2022

The Common Lisp loop macro is the most incredible thing I’ve seen so far when working with functional PL. It comes with so subtle variations that it is hard to summarize all of its functionalities in a few words. Let’s consider the following two statements:

(loop for i in '(0 1 2 3 4) do (format t "~a " i))
;; => 0 1 2 3 4
(loop for i on '(0 1 2 3 4) do (print i))
;; => (0 1 2 3 4)
;; => (1 2 3 4)
;; => (2 3 4)
;; => (3 4)
;; => (4)

The first instruction asks to iterate over each element of a list, while in the second we iterate over each cdr of a list. If you want to return a list for later consumption, you can use (append (list i i)) or simply “collect” the elements, but see below. Any other builtin or user function that accept the iterator variable (i) as an argument will work too. Little subtelties…

I don’t know if the loop macro in Common Lisp is genius or madness. It’s the idiomatic way to do iteration, but the syntax doesn’t resemble Lisp in any way, the number of variations is as long as your arm, and you can mix and match every which way. — A Closed and Common Lisp

A standard for loop construct is written:

(loop for i from 0.0 to 4.0 by 0.5 do (format t "~a " i))
;; => 0.0 0.5 1.0 1.5 2.0 2.5 3.0 3.5 4.0

Note that loop works with real number too, unlike some other functions or macros, and that you are not limited to generating sequences of increasing values – replace start and end value and to with downto to get sequence in descending order.1 If you are an Alexandria’s user, you probably already know the iota function, which helps in generating sequence of equally spaced integers (think of Python’s range function). Here it is in action, and its equivalent version using loop using keyword parameters:

(iota 5)
;; => (0 1 2 3 4)
(loop :for n :below 5 :collect n)
;; => (0 1 2 3 4)

The loop macro also supports conditional statement in between, like in (loop for i from 1 to 10 when (oddp i) collect i).

The only contender to CL’s loop macro is Racket’s iterations and comprehensions forms, which I use a lot for little scripts. Chicken Scheme and Clojure also have for loop constructs which are pretty handy. Many times, though, for loops are not really needed and can be safely replaced with map or fold, or variations thereof, even in Python. There was an interesting discussion on for loop in Scheme on the IRC #scheme channel recently, but I forgot to bookmark the conversation. It’s probably in the log, though.

If you are interested in macros, I can suggest the following blog posts:

♪ Brad Mehldau Trio • The Very Thought of You

  1. They are known as the arithmetic-up and arithmetic-downto arguments. See the HyperSpec documentation↩︎

See Also

» NewLISP and memoization » Sequences in Lisp » Common Lisp Hyperspec in Vim » Generating power sets in Lisp » QR factorization and linear regression