Range Over Iterators in ActionScript

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

package {
    import flash.utils.Dictionary;

    public class List {
        private var head:Element;
        private var tail:Element;

        public function List() {
            this.head = null;
            this.tail = null;
        }

        public function push(value:*):void {
            var newElement:Element = new Element(value);
            if (this.tail == null) {
                this.head = newElement;
                this.tail = newElement;
            } else {
                this.tail.next = newElement;
                this.tail = newElement;
            }
        }

        public function all():Function {
            return function(yield:Function):void {
                for (var element:Element = head; element != null; element = element.next) {
                    if (!yield(element.value)) {
                        return;
                    }
                }
            };
        }
    }

    internal class Element {
        public var next:Element;
        public var value:*;

        public function Element(value:*) {
            this.value = value;
            this.next = null;
        }
    }
}

function genFib():Function {
    return function(yield:Function):void {
        var a:int = 1;
        var b:int = 1;
        while (true) {
            if (!yield(a)) {
                return;
            }
            var tmp:int = a;
            a = b;
            b = tmp + b;
        }
    };
}

function main():void {
    var lst:List = new List();
    lst.push(10);
    lst.push(13);
    lst.push(23);

    // Iterating over the list using the iterator
    var iterator:Function = lst.all();
    iterator(function(value:*):Boolean {
        trace(value);
        return true;
    });

    // Collecting all values into an array
    var all:Array = [];
    iterator(function(value:*):Boolean {
        all.push(value);
        return true;
    });
    trace("all:", all);

    // Iterating over Fibonacci numbers
    var fibIterator:Function = genFib();
    fibIterator(function(value:int):Boolean {
        if (value >= 10) {
            return false;
        }
        trace(value);
        return true;
    });
}

main();

All returns an iterator, which in this language is a function.

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 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.

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

var iterator:Function = lst.all();
iterator(function(value:*):Boolean {
    trace(value);
    return true;
});

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

Here’s the output you can expect from running the code:

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