forEach on next

# Kris Kowal (15 years ago)

[moving this thread onto es-discuss]

On Tue, Mar 23, 2010 at 2:45 PM, Brendan Eich <brendan at mozilla.org> wrote:

On Mar 23, 2010, at 2:17 PM, Kris Kowal wrote:

Aside: I hope we can resolve MarkM's suspicions of composability problems with generators; they are one of my very favorite features of Python: like UNIX pipes, composability is their middle-name.  The PEP he referenced some time ago just provides more terse syntax for chained generators; I find that this is a nicety but not a demonstration that the simple "yield" is flawed.

Yes, just because something doesn't sing and dance  doesn't mean it is not a good actor ;-).

I also hope that ECMAScript opts to follow the Python iterator protocol more closely than JavaScript 1.6—for chained lazy iterations, "next" needs to be the most discrete layer of the protocol, with "forEach" implemented in terms thereof.

JS1.6 was forEach and other Array extras.

JS1.7 introduced generators and iterators, and yes, we didn't incompatibly change the Array extras to treat iterators as arrays. We also didn't introduce new libraries, e.g. Python itertools workalikes. It's easy enough to do this in user-land, and hard for the TC39 committee to get  library design right.

Thanks for the clarification.

At this point how would you make forEach and other Array extras work on iterators?

I'm getting all sorts of alarms going off in my head to the tune of "this is all obvious; I probably missed something important. Like, this is how it works already." I've heard complaints about for each loops stalling to collect though, so here are my thoughts about iterators anyway:

I was just looking at some transcoding stream stuff, which follows the general form of Python's iterator protocol. The notion is that "next" is the most atomic unit of the iterator protocol; if you have an object that implements "iterator", that returns an object that implements "next", you get "forEach" and friends for free. This is important because "next" is "pausable" (or progress on explicit request, really), and "forEach" is not. This means that you can create incrementally iterable generics like "mapIterator" and thereby create chains of decorated iterators that can operate on iterations of indefinite length, and "forEach" doesn't have to collect the values of the iterable before entering its loop.

/***

  • @this {Object} */ SomethingAutoIterable.prototype.iterator = function () { return this; };

/***

  • @this {{iterator}} any iterable object. iterator must
  • return an object with a next method that returns the
  • next element of the iteration or throws StopIteration. */ SomethingAutoIterable.prototype.forEach = function (block, that) { var line; var iterator = this.iterator(); while (true) { try { line = iterator.next(); } catch (exception) { if (exception === StopIteration) break; throw exception; } block.call(that, line); } };

If you're crazy, can optimize the crap out of the try/catches (presumably by removing them for a small set of common types), and want to take things one step further than Python, you can implement both "continue" and "break" semantics by adding SkipIteration.

…forEach = function (relation, that) { var iterator = this.iterator(); try { while (true) { try { relation.call(that, iterator.next()); } catch (exception) { if (exception !== SkipIteration) { throw exception; } } } } catch (exception) { if (exception !== StopIteration) { throw exception; } } };

And, if you're really crazy, you can implement breaking and continuing outer loops by associating individual StopIteration and SkipIteration instances with each iterator. By really crazy, I mean that my PEP got shot down, so I wouldn't be surprised by a repeat success.

Kris Kowal