Another Useful JavaScript Closure

| 9 Comments

I found another useful example involving closures. Bob Ippolito's python-style iteration framework includes this nifty code:

arrayLikeIterator = function (arrayLike) {
    var i = 0;
    return {
        'next': function () {
            if (i >= arrayLike.length) {
                throw StopIteration;
            }
            return arrayLike[i++];
        }
    };
};

There is lots more to Bob's iteration framework at his blog. The iteration stuff is part of Bob's larger framework, MochiKit, which looks quite interesting.

For the record, I disagree with Bob about throwing an exception at the end of iteration. I tihnk he'd be better of returning a unique sentinel value (but probably not null). His StopIteration error object could work just fine as a sentinel, if he'd return it instead of throwing it.

Update: after exchanging a number of emails with Bob, I now understand that his iterators are python-style iterators, intended to be used as part of a larger framework, and that they are to be treated as opaque values and passed to other functions that do the work of iterating them. That is, the end user of the iterators never has to catch the exception that the iterator throws. Bob has convinced me when an iterator is processed by a chain of composed functions, the use of an exception to end the iterator is the most efficient way to go. So, I drop my objection to throwing an exception to end iteration, as long as you use Bob's iterators in the python-based style he intends.

9 Comments

I use a somewhat different style of iterator: this one allows retrieving the current element from the iterator any number of times, while calling .next() on it will advance the iterator:

http://plasmasturm.org/log/217/

I never gave much thought to the ambiguity of the value being returned, though. That’s a good point, and one that could easily be accomodated with the style I advocate there, by just returning true from .next() as long as there’s something to iterate over. It would also neatly solves the redundancy of having two ways to retrieve the element in my style.

Neither the C++ style iterators or the sentinel iterators are appropriate for JavaScript.

The C++ style iterator requires too many function calls. You need to do 2N function calls to iterate over a sequence of N length. Dumb, and slow.

The sentinel style iterator is too fragile. If you iterate over something that happens to contain StopIteration (i.e. values(MochiKit.Iter)), then you're screwed. It'll stop at a random place and you won't know why. Also, you'll be doing N additional comparisons, though comparisons are typically fast so this probably doesn't really matter.

There is some number N where throwing 1 exception is more expensive than N comparisons.. but for larger N, exceptions get more and more sensible. Especially for deep filter stacks.

In other words, exception based iterators are The Only Way To Do It. In general, modeling something after Python is the RIght Way, and this is certainly one of those cases. If you don't agree, you just haven't thought about it enough :)

Bob: good point, but I don't agree with the conclusion. You can *still* avoid exceptions without losing any fragility -- the keyword is "lists." See http://plasmasturm.org/log/312/ where I've written about it in more detail.

Yes, you can create N proxy objects (arrays in your case, but it could be anything) instead of doing N function calls. These are equivalent behaviors. The difference is that with C++ style iterators, the iterator itself is the proxy. Now you have N proxy objects. Lame!

Exceptions are not a bad thing for flow control. Get over it. They make everything much simpler. Instead of checking every single value returned, you simply write code that deals with all values. The exception will "break" out of the "loop" and let you get on with your business.

Yes, you can create N proxy objects (arrays in your case, but it could be anything) instead of doing N function calls. These are equivalent behaviors. The difference is that with C++ style iterators, the iterator itself is the proxy. Now you have N proxy objects. Lame!

Exceptions are not a bad thing for flow control. Get over it. They make everything much simpler. Instead of checking every single value returned, you simply write code that deals with all values. The exception will "break" out of the "loop" and let you get on with your business.

Okay sure then, let me eliminate all but one proxy objects:

function iterate( iterable ) {
var i = -1;
var a = [];
return function() {
return ++i < iterable.length ? a = [ iterable[ i ] ] : null;
};
}

There we go.

Sorry, try{ } catch{ } for control flow is lame. I'm no Joel Spolsky, but he largely has a point when he says they're invisible GOTOs.

Err, sorry,

return ++i < iterable.length ? a[0] = iterable[ i ] : null;

Ugh. Need more coffee. That should, of course, be

return ++i < iterable.length ? ( a[ 0 ] = iterable[ i ], a ) : null;

This time tested and works.

Thanks Aristotle and Bob. This is the best discussion I've had in the comments section of my blog!

Bob: I think that the fagility of using a sentinel value is overstated. While it is possible to construct a case where the sentinel is one of the values to be iterated, I think this would be very rare in practice.

Aristotle: using an array is a nice workaround to the problem. But I wouldn't like to have to remember the [0] each time!

Bob: you argue that the problem with using a separate hasNext() method is that it requires 2n calls. But your python-style alternative is to use a forEach() method to which you pass an iterator and a function. forEach will call the iterator's next() method and then call the function you supply. That's 2n calls right there!.

The real difference is that your iterators are designed for python style programming and I'd prefer iterators that were more Java-style. I'd like to write the forEach loop myself, and put the code right there in the body of the loop, rather than putting the code into a function that I pass to another function. I could use a hasNext() style iterator with the same 2n function calls as you have.

Thanks to both of you for the good discussion!

Books

Comprehensive coverage of Ruby 1.8 and 1.9

"The New Most Important Ruby Book"
Peter Cooper,
rubyinside.com

Completely updated for Ajax and Web 2.0

"A must-have reference"
Brendan Eich,
creator of JavaScript

The classic Java quick-reference

Advertising

Pages

Hosted By

Powered by Movable Type 4.21-en