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:
cons
: alias forforEachCons
reverse
: alias forreverseForEach
slice
: alias forforEachSlice
withIndex
: alias forforEachWithIndex
withObject
: alias forforEachWithObject
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]