Array extras functionality for iterators

# Domenic Denicola (12 years ago)

ES5's existing array extras make working with arrays a joy.

However, sometimes arrays are not the right tool for the job. Perhaps you want lazy evaluation semantics (generators). Or perhaps you want to communicate that the list is immutable (compare .NET's IEnumerable<T> or Java's Iterable<T>). ES Harmony seems to have the answer: iterators! Like IEnumerable<T> or Iterable<T>, they are the most basic primitive of iteration. Yay!

But, if my fetchAllProducts() method returns an iterator, I don't get my array extras. Sad.


This may be solvable in library-space, but the iterator proposal doesn't seem to have an Iterator.prototype I could extend. So we end up with unfortunate underscore-style wrappers:

_(iterator).chain().map(mapper).some(predicate).value() .some(.map(iterator, mapper), predicate)

I propose adding the array extras to any iterator (in some way), such that we can have syntax similar to the following:

iterator.map(mapper).some(predicate) // returns an iterator

The methods I would like to see are:

  • every, filter, forEach, map, reduce, reduceRight, some
  • Optionally: join, toString
  • Controversially: sorted, reversed (non-mutating versions of sort and reverse)

What do you think?

# Michael A. Smith (12 years ago)

Sorry for the resend. Meant to include the list.

I like this idea a lot! However, what would be the correct behavior of a method like 'every' on an infinite generator?

-Michael A. Smith

# Xavier MONTILLET (12 years ago)

I'd say every and other methods like that should take another argument that specifies the number if times to test.

You could say that every returns:

  • true if the generator is finite and the function always returned true
  • false if one of the finite (either because the generator is or because a number was given as argument) number of calls of the function returned false
  • undefined if the generator is infinite and none of the finite number of calls returned false

OR

return a generator that tests a new value and returns either true or false every time it is called on the nth call, it tests n values

map and filter clearly would be very useful. I'm not sure about the others.

# Domenic Denicola (12 years ago)

I'd say every and other methods like that should take another argument that specifies the number if times to test.

This seems reasonable. Preferably that parameter would be optional, since the majority of the time my iterators will not be derived from infinite generators (and I know this). Otherwise I'd just keep passing Infinity as the third argument to my methods.

Is it possible to detect infinite generators ahead of time? My intuition says no.

map and filter clearly would be very useful. I'm not sure about the others.

Losing the others would be a shame. As I mentioned, much of the time I want to expose something with the semantics of an iterator, even if internally to my module or class I am storing the data as an array. So even if infinite generators cause problems for implementing every et al., the vast majority of real-world iterators would not be derived from infinite generators. Thus my above desire for the extra argument being optional.

Currently in our codebase, we have a number of these methods (like fetchAllProducts()) that return slice()ed copies of internal arrays, since we don't want to expose the arrays directly. (Iterators would of course be the best solution here.) Over the course of 77K LOC, the only methods we don't use on the products collection are join, reduceRight, reverse, and toString. And we do use join on some of the simpler collections (fetchProductTags()). So they're all useful!

# Brendan Eich (12 years ago)

Has anyone checked out or used docs.python.org/py3k/library/itertools.html ? Seems worth considering since ES6 generators are derived from Python generators.

Indeed, there's no way to detect an infinite generator ahead of time. Some array extras do not make sense even for very large arrays. Again I cite Peter Norvig's constraint-propagating-search-based Sudoku solver, which I ported to JS1.8:

bug380237.bugzilla.mozilla.org/attachment.cgi?id=266577

It definitely requires generators not arrays in places, to avoid exponential space blow-up.

# Michael A. Smith (12 years ago)

On a related note, there are many ways in which some of these methods would overlap with the capabilities of array comprehensions and generator expressions. As far as I can tell, the one thing you cannot do (with, say, map) is take an array as input and process it lazily.

For example: someVeryLargeArray.map(someFunction); // guarantees an array result, cannot be lazy

vs

(someFunction(someVeryLargeArray(i), i, someVeryLargeArray) for (i in someVeryLargeArray)) // Lazy, likely to perform better in some important cases.

I think there's a case to be made for direct implementations of such methods. Something like

someVeryLargeArray.iMap(someFunction); // Lazy, guaranteed only to be iterable

(No apologies to the email protocol.)

What do you think?

-Michael A. Smith

# Domenic Denicola (12 years ago)

someVeryLargeArray.iMap(someFunction); // Lazy, guaranteed only to be iterable

How about someVeryLargeArray.asIterator().map(someFunction)?

# Yehuda Katz (12 years ago)

Catching up on this discussion.

Personally, I'd like to see us come to quick consensus on forEach for collections, because without something like forEach, it's impossible to implement useful versions of these collections in user-space in browsers that do not implement for-of.

Indeed, the current browsers that implement Map and Set do not implement for-of, which means that even though browsers implement the collections (and Mozilla has for a bit now), they are not very useful because they cannot be iterated over. Had forEach been part of the initial spec, I would be using them today.

One last thing: forEach on Map should probably pass the key and value to the function.

Yehuda Katz (ph) 718.877.1325

# Rick Waldron (12 years ago)

On Tue, Feb 28, 2012 at 12:06 PM, Yehuda Katz <wycats at gmail.com> wrote:

Catching up on this discussion.

Personally, I'd like to see us come to quick consensus on forEach for collections, because without something like forEach, it's impossible to implement useful versions of these collections in user-space in browsers that do not implement for-of.

Indeed, the current browsers that implement Map and Set do not implement for-of, which means that even though browsers implement the collections (and Mozilla has for a bit now), they are not very useful because they cannot be iterated over. Had forEach been part of the initial spec, I would be using them today.

I've had the same experience that Yehuda describes - it's almost comical that Chrome blog published an announcement for the support of Map and Set, beyond...

var s = new Set() "undefined" s.add("foo") "undefined" s.has("foo")

true

s.delete("foo") "undefined"

...They are useless.

One last thing: forEach on Map should probably pass the key and value to the function.

+1 to Map.prototype.forEach receiving key and value as arguments