Range Over Iterators in Racket

Starting with version 1.23, support for iterators has been added, 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 slice of all elements in the list. With iterators, we can do it better - as shown below.

#lang racket

(struct element (next val))

(struct List (head tail)
  #:transparent)

(define (push! lst val)
  (let ([new-element (element #f val)])
    (if (not (List-tail lst))
        (begin
          (set-List-head! lst new-element)
          (set-List-tail! lst new-element))
        (begin
          (set-element-next! (List-tail lst) new-element)
          (set-List-tail! lst new-element)))))

(define (all lst)
  (lambda (yield)
    (let iter ([e (List-head lst)])
      (when e
        (when (yield (element-val e))
          (iter (element-next e)))))))

(define (gen-fib)
  (lambda (yield)
    (let loop ([a 1] [b 1])
      (when (yield a)
        (loop b (+ a b))))))
    
(define lst (List #f #f))
(push! lst 10)
(push! lst 13)
(push! lst 23)

(for ([e (in-producer (all lst) #f)])
  (printf "~a\n" e))

(let ([all (for/list ([e (in-producer (all lst) #f)])
             e)])
  (printf "all: ~a\n" all))

(for ([n (in-producer (gen-fib) #f)])
  (when (>= n 10) (break))
  (printf "~a\n" n))

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

Struct element contains the next element and a val, while the List struct contains the head and tail elements.

We define a push! function to add elements to the list, initializing new elements if the list tail is #f, or appending to the existing tail otherwise.

The all function generates an iterator. It takes a yield function as a parameter, calling yield for every element we want to iterate. The iteration terminates early if yield returns #f.

Iterators don’t require an underlying data structure and don’t even have to be finite. Here’s a function returning an iterator over Fibonacci numbers; it keeps running as long as yield returns #t.

After creating a List and pushing values (10, 13, 23), we use the all function in a regular for loop to print each element.

We can collect all values from the iterator into a list and print them.

Finally, we use the Fibonacci iterator and break the loop when a number is greater than or equal to 10, printing each number up to that point.