Pythonic Iterators With Mootools
ython comes standard with an interesting module called, itertools, which provides a set of functions that make performing complex computations that revolve around iteration much easier. It is actually a invaluable set of tools to have at your disposal when you get comfortable with them. The module itself can work with anything that implements the iterator interface. An iterator is an object wrapper around some sequence that exposes a method called next. When the next method is called, the iterator should return the next item in the sequence and throws a StopIteration exception when there is nothing left in the sequence. While JavaScript doesn't offer anything that would resemble an iterator, that are actually very simple to implement when the Mootools class system
Iterator Class
/**
* Iterable Class
* @class
* @param {Object} options Defines the next, toString, and repr methods
*/
const Iterable = new Class({
Implements: [Events, Options]
, options: {
next: function(){ }
, repr: function(){
return "<Iterable: >";
}
, toString: function(){
return this.repr();
}
}
, initialize: function( options ){
options = options || {};
this.setOptions( options );
}
/**
* Returns the next object in the iterable.
* This Method should be overridden to change how the iterable behaves
* @returns {Mixed} obj The next object
*/
, next: function(){
const n = this.options.next();
this.fireEvent( 'next', n);
return n;
}
/**
* Returns the next object in the iterable
* @returns {String} String Returns a String alternative for this iterable. Used by the log module
**/
, toString:function(){
return this.options.toString();
}
/**
* Returns the next object in the iterable
* @returns {String} repr the string representation of this iterable
*/
, repr: function(){
return this.options.repr();
}
});
Python doesn't have an Iterator class, but what it does do is define an interface that, when implemented, allows an object to be Iterable. The above class does the same really. We simply define a class that has place holders for a custom next, and toString method. The Tricky part is knowing when to stop. In Python, Iterable objects throw a StopIteration exception which are, in most situations caught silently and allow the program to continue. This is a little more difficult to do in JavaScript. What we need to do is create a custom Error class that we can later check for.
StopIteration Error
'use strict'
const NamedError = function( name, message ){
this.message = message || "Object Not Found"
this.name = name;
}
NamedError.prototype = new Error();
Now we can update the Iterator Class to throw the StopIteration from the next method by default which can be checked for explicitly.
'use strict'
const StopIteration = new NamedError(
'StopIteration'
, 'End of iteration reached'
)
const Iterable = new Class({
Implements: [Events, Options]
, options: {
next: function(){
throw StopIteration
}
, repr: function(){
return "<Iterable: >";
}
, toString: function(){
return this.repr();
}
}
// snip...
})
Why
So, why would you want to define a class that doesn't really do anything. Well, a class like this really defines a pattern. Similar to the way an interface defines structure, this set up defines how an end user would actually implement the Iterator class. They need to implement the next function so it either takes some action or returns some value and when it is no longer able to do so, it throws StopIteration.
How
You can use the Iterator class to create a rather wide set of useful tools that deal with iteration. Take a look over the itertools module from python. There are all thing that utilize the iterable pattern. For example, The cycle method from itertools - Given a finite list, calling the next function will return the next item in the list. If it reaches the end of the list, it will start over. Here is how you might go about doing that.
Firstly,we dive in we want to set up a function that can take something that javascript can iterate over ( object or array ) and convert it into an Iterable Class instance.
'use strict'
function from(obj) {
switch (obj) {
case 'array':
return (function() {
let arr = obj
let position = 0
return new Iterable({
next: function() {
if (position < arr.length) {
return arr[ position++ ]
} else {
throw StopIteration
}
}
, repr: function() {
return '<Iterable: ' + arr.join(', ') + '>'
}
})
}())
case 'object':
return (function() {
let o = obj
, position = 0
, keys = object.keys(o)
return new Iterable({
next: function() {
if (position < keys.length) {
return o[ keys[position++] ]
} else {
throw StopIteration
}
}
, repr: function() {
return '<Iterable: ' + keys.join(', ') + '>'
}
})
}())
default:
return new Iterable({})
}
}
From here we define the cycle which returns a new Iterable that wraps around the one we created and checking for StopIteration errors to do something interesting.
'use strict'
function cycle(item) {
const list = []
const iterator = from(item)
return new Iterable({
repr: function() {
return 'cycle( x1, x2, x1 ... )'
}
, toString: function() {
return '<Iterable: cycle ' + list.join(', ') + '>'
}
, next: function() {
let rvalue, x
try {
rvalue = iterator.next()
list.push(rvalue)
return rvalue
} catch (e) {
if (e != StopIteration) {
throw e
}
// if we hit a StopIteration we want the last item on the list
if (!list.length) {
this.next = function() {
throw StopIteration
}
} else {
x = -1
/**
* crazy!
* dynamically replace the next function to iterate over the cached values.
**/
this.next = function() {
// x is defined outside the closure so it will be
// whatever we leave it at.
x = (x + 1) % list.length
return list[ x ]
}
}
}
return this.next()
}
})
}