Explain Clojure’s “for”, line by line
Recently I came across a rant on Reddit about clojure’s unfriendly documentation. One example mentioned is the documentation of
for. The official doc reads as follows:
List comprehension. Takes a vector of one or more binding-form/collection-expr pairs, each followed by zero or more modifiers, and yields a lazy sequence of evaluations of expr. Collections are iterated in a nested fashion, rightmost fastest, and nested coll-exprs can refer to bindings created in prior binding-forms. Supported modifiers are: :let [binding-form expr …], :while test, :when test.
(take 100 (for [x (range 100000000) y (range 1000000) :while (< y x)] [x y]))
It looks intimidating.But fear not! If you’re new to clojure and looking to learn how to use
for in clojure, here’s a line-by-line explanation of what the official doc means.
1. List comprehension.
for does list comprehension, which is slightly different from those
seq in clojure’s case) by evaluating the body, then collecting the result of each iteration. For example,
(for [x (range 5)] (+ x 2)) gives you
(2 3 4 5 6). (Recall that
range generates a sequence of integers).
2. Takes a vector of one or more binding-form/collection-expr pairs…
“Binding-form” means “loop variable”(although in clojure, variables are by default immutable). “Collection-expr” is the collection you want to loop through. Any
seq can be iterated, such as vectors, lists, and maps(as a sequence of key-value pairs). You can learn more about
3. …each followed by zero or more modifiers…
Sometimes you may want to terminate the loop early, or skip certain values. We’ll come back to “modifiers” near the end of this article.
4. …and yields a lazy sequence of evaluations of expr.
Sequences constructed with
for are lazy, which means that the actual computation is delayed until you need to look at the value of it(e.g. you want to print it to the REPL). In addition to being more efficient, laziness makes it possible to work with *infinite sequence*, i.e. sequences that have an infinite number of values in it, by limiting calculation to what you really need. Sounds like magic, right?
5. Collections are iterated in a nested fashion…
That is, you can use multiple loop variables similar to a nested
for loop in other languages. For example,
(for [x [1 2 3] y [2 3 4]] (* x y)) gives you
[2 3 4 4 6 8 6 9 12] .
6. …rightmost fastest…
“Fastest” means “the most inner loop”. Note that we often add extra newlines between loop variables to improve readability, so “rightmost” can sometimes means “bottom-most”, like this:
7. …and nested coll-exprs can refer to bindings created in prior binding-forms.
This is best illustrated with an example.
8. Supported modifiers are: :let [binding-form expr …], :while test, :when test.
This is the part where the doc fails us. What are these modifiers? What are they good for? After some experimenting with the REPL, here’s a summary:
:letworks as a local binding, similar to clojure’s
:whenworks as a filter, skipping cases where loop variables don’t satisfy a condition(like
continuein C or Java)
:whenworks as a test, ending the current loop early if loop variables don’t satisfy a condition(like
breakin C or Java). Notice that it doesn’t end the entire
forloop, only the inner-most loop.
These modifiers are illustrated in the following example:
Finally, the doc provides one example of usage. Try understand what it does, and use the REPL to check the answer. You can find more examples here.
And that’s it for the doc! If you’ve made it to the end, you are doing great! Clojure’s
for is concise and expressive. I hope you have a good time using it!