Range Over Iterators in Clojure

Starting with version 1.23, the language has added support for iterators, which lets us range over pretty much anything!

Let’s look at the List type from the previous example again. In that example, we had an AllElements method that returned a list of all elements in the list. With the new iterators, we can do it better - as shown below.

(defrecord List [head tail])
(defrecord Element [next val])

(defn push [lst v]
  (if (nil? (:tail lst))
    (do
      (set! (.head lst) (Element. nil v))
      (set! (.tail lst) (:head lst)))
    (do
      (set! (.next (:tail lst)) (Element. nil v))
      (set! (.tail lst) (:next (:tail lst))))))

(defn all [lst]
  (fn [yield]
    (loop [e (:head lst)]
      (when (and e (yield (:val e)))
        (recur (:next e))))))

(defn gen-fib []
  (fn [yield]
    (loop [a 1 b 1]
      (when (yield a)
        (recur b (+ a b))))))

Since all returns an iterator, we can use it in a regular loop.

(let [lst (->List nil nil)]
  (push lst 10)
  (push lst 13)
  (push lst 23)
  (doseq [e (all lst)]
    (println e)))

(let [fib-seq (gen-fib)]
  (doseq [n fib-seq]
    (when (>= n 10)
      (return))
    (println n)))

Iteration doesn’t require an underlying data structure, and doesn’t even have to be finite! Here’s a function returning an iterator over Fibonacci numbers: it keeps running as long as yield keeps returning true.

Once the loop hits the break or an early return, the yield function passed to the iterator will return false.