Range Over Iterators in Groovy

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

class List<T> {
    Element<T> head, tail

    void push(T v) {
        if (tail == null) {
            head = new Element<>(v)
            tail = head
        } else {
            tail.next = new Element<>(v)
            tail = tail.next
        }
    }

    Iterable<T> all() {
        return { -> 
            new Iterator<T>() {
                private Element<T> current = head

                boolean hasNext() {
                    return current != null
                }

                T next() {
                    T value = current.value
                    current = current.next
                    return value
                }
            }
        }()
    }
}

class Element<T> {
    T value
    Element<T> next

    Element(T value) {
        this.value = value
        this.next = null
    }
}

All returns an iterator, which in this case is an instance of java.util.Iterator.

The iterator function takes another function as a parameter, called yield by convention (but the name can be arbitrary). It will call yield for every element we want to iterate over, and note yield’s return value for a potential early termination.

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.

Iterable<Integer> genFib() {
    return {
        new Iterator<Integer>() {
            private int a = 1, b = 1

            boolean hasNext() {
                return true
            }

            Integer next() {
                int result = a
                int nextValue = a + b
                a = b
                b = nextValue
                return result
            }
        }
    }()
}

We can then use these iterators with a for- in loop to print values or to collect them into a list.

def main() {
    def lst = new List<Integer>()
    lst.push(10)
    lst.push(13)
    lst.push(23)

    for (e in lst.all()) {
       println(e)
    }

    def all = lst.all().collect()
    println("all: " + all)

    for (n in genFib()) {
        if (n >= 10) break
        println(n)
    }
}

main()

Output:

10
13
23
all: [10, 13, 23]
1
1
2
3
5
8