Enumerator

The Enumerator class, part of the Enumerable module, encapsulates instances of iterative processes, allowing them to be reused and composed in useful ways.

// In the browser
JS.require('JS.Enumerator', function(Enumerator) { ... });

// In CommonJS
var Enumerator = require('jsclass/src/enumerable').Enumerator;

Most methods in the Enumerable API that take an iteration function will return an Enumerator if called without said function. The Enumerator encapsulates the object being iterated over, the iteration method used and any arguments appearing before the iterator block. Enumerators are generated using the Kernel#enumFor method, for example here are a couple of generators that appear in the Enumerable API:

map: function(block, context) {
    if (!block) return this.enumFor('map');
    // map code
}

forEachCons: function(n, block, context) {
    if (!block) return this.enumFor('forEachCons', n);
    // forEachCons code
}

So what can you do with an Enumerator? Well, instances of this class implement the Enumerable API, allowing you to use any enumeration method in combination with the method that produced it. Let’s take an example. Suppose we have this basic class that wraps an array and allows us to iterate over it using forEach:

var Collection = new Class({
    include: Enumerable,

    initialize: function() {
        this._list = [];
        for (var i = 0, n = arguments.length; i < n; i++)
            this._list.push(arguments[i]);
    },

    forEach: function(block, context) {
        if (!block) return this.enumFor('forEach');

        for (var i = 0, n = this._list.length; i < n; i++)
            block.call(context, this._list[i]);

        return this;
    }
});

Notice how forEach supplies only the current item to the iterator block, not the current index. What if we wanted to map over a collection using index values? Enumerators make this possible; since Enumerable#map returns an Enumerator, we can combine it with forEachWithIndex to get what we want:

var list = new Collection(3,7,4,8,2);

list.map().forEachWithIndex(function(x,i) { return x + i })
// -> [3, 8, 6, 11, 6]

These can be switched around, so you can do various things using indexes:

list.forEachWithIndex().map(function(x,i) { return x + i })
// -> [3, 8, 6, 11, 6]

list.forEachWithIndex().select(function(x,i) { return x < i })
// -> [2]

Enumerator provides some shorthands for common iteration methods, they are:

These help to clarify chains, for example we can rewrite the last expression as:

list.select().withIndex(function(x,i) { return x < i })
// -> [2]

We’ll end with a few more examples to show how iterations can be composed using the Enumerator class:

list.forEachCons(3).forEach(function() {
    console.log(arguments);
});
// output:
//      [[3, 7, 4]]
//      [[7, 4, 8]]
//      [[4, 8, 2]]

list.forEachCons(3).withIndex(function() {
    console.log(arguments);
});
// output:
//      [[3, 7, 4], 0]
//      [[7, 4, 8], 1]
//      [[4, 8, 2], 2]

list.forEachCons(3).reverse(function() {
    console.log(arguments);
});
// output:
//      [[4, 8, 2]]
//      [[7, 4, 8]]
//      [[3, 7, 4]]

list.forEachCons(3).reverse().withIndex(function() {
    console.log(arguments);
});
// output:
//      [[4, 8, 2], 0]
//      [[7, 4, 8], 1]
//      [[3, 7, 4], 2]

list.reverseForEach().slice(2).cycle(4).withIndex(function() {
    console.log(arguments);
});
// output:
//      [[2, 8], 0]
//      [[4, 7], 1]
//      [[3], 2]
//      [[2, 8], 3]
//      [[4, 7], 4]
//      [[3], 5]
//      [[2, 8], 6]
//      [[4, 7], 7]
//      [[3], 8]
//      [[2, 8], 9]
//      [[4, 7], 10]
//      [[3], 11]