I challenged myself to develop something functionally similar to Python's list-comprehension. Below is what I came up with. Obviously, Javascript doesn't have the elegant syntax, so
ll = [x for x in range(15) if x % 3 == 0]
becomes
var ll = genList(rangeGenerator(0, 15), function (x) { return x % 3 === 0; });
I used the namespace pattern mentioned in item #5 on this site (as of 12/26/2012): http://javascriptweblog.wordpress.com/2010/12/07/namespacing-in-javascript/. Perhaps I should have used a module pattern, such as written here http://www.adequatelygood.com/2010/3/JavaScript-Module-Pattern-In-Depth (as of 12/26/2012).
All comments are welcome, but I have a specific list of questions:
Is there a memory concern in "InArrayGenerator" holding a reference to the "list" argument? I realize there's potential for the contents of "list" to change if there is code between the creation of the generator and its consumption. This could be mitigated with a deep copy, if needed.
How could "InArrayGenerator" be modified to safely operate on objects (e.g. iterate over the properties of the object)? One thought is to capture the object's properties into an array during "constructor" time. Of course, if there's code between the creation and consumption of the generator, this could potentially lead to a difficult to track error.
Is there a better way to effect the inheritance model? The reason for the inherit misdirection is that InArrayGenerator inherits from RangeGenerator, which has constructor arguments that can't be invoked when defining the prototype. Perhaps it would be better for InArrayGenerator to contain a RangeGenerator instead.
Is there a slicker way to handle the optional parameters in genList?
Is is bad to use magic "undefined" to indicate end of iteration? I thought of throwing an exception following the Python style of raising an exception, but that sounded expensive (in truth, I need to do some research on the expense of Javascript's throw). I also thought of defining hasNext(), but it seemed redundant.
GeneratorModule.js
/*jslint plusplus: true, vars: true, browser: true, devel: false, maxerr: 5, maxlen: 140 */
//inspired by python list comprehension (x for x in range(10))
function GeneratorModule() {
"use strict";
function inherit(parentPrototype) {
function F() {}
F.prototype = parentPrototype;
return new F();
}
function RangeGenerator(min, max) {
this.current = min;
this.max = max;
}
RangeGenerator.prototype.next = function () {
var nn = this.current;
if (nn === this.max) { return undefined; }
this.current += 1;
return nn;
};
function InArrayGenerator(list) {
RangeGenerator.call(this, 0, list.length);
this.list = list;
}
InArrayGenerator.prototype = inherit(RangeGenerator.prototype);
InArrayGenerator.prototype.next = function () {
var nn = RangeGenerator.prototype.next.call(this);
if (nn === undefined) { return undefined; }
return this.list[nn];
};
this.getGeneratorModuleVersion = function () {
return 1.0;
};
// Iterator for an array.
this.arrayGenerator = function (list) {
return new InArrayGenerator(list);
};
// Generate a sequence min <= x < max. Optionally filter sequence
// by predicate: function (item) { return boolean; }
this.rangeGenerator = function (min, max) {
return new RangeGenerator(min, max);
};
// Generate an array from any generator with optional filtering by
// predicate: function (item) { return boolean; } and with optional
// morphing of the elements from the generator into another data type.
// If !morph, return elements from the generator as is.
this.genList = function (generator, predicate, morph) {
if (!predicate) { predicate = function (x) { return true; }; }
if (!morph) { morph = function (x) { return x; }; }
var ll = [];
while (true) {
var gg = generator.next();
if (gg === undefined) { break; }
if (predicate(gg)) { ll.push(morph(gg)); }
}
return ll;
};
}
test.js
function test() {
"use strict";
function reportNumber(number) {
return number + " is " + (number % 2 === 0 ? "even" : "odd") + ".";
}
function reportLetter(letter) {
return "The letter is " + letter + ".";
}
function isMultipleOf3(number) {
return number % 3 === 0;
}
var app = {};
GeneratorModule.call(app);
console.log(app.genList(app.rangeGenerator(0, 10)));
// [0,1,2,3,4,5,6,7,8,9]
console.log(app.genList(app.rangeGenerator(0, 15), isMultipleOf3, reportNumber));
// ["0 is even.", "3 is odd.", "6 is even.", "9 is odd.", "12 is even."]
console.log(app.genList(app.arrayGenerator(["a", "d", 'q'])));
// ["a", "d", "q"]
console.log(app.genList(app.arrayGenerator(["a", "d", 'q']), function (x) { return x === "a"; }, reportLetter));
// ["The letter is a."]
}
test();