2

It seems utterly natural to me that generators, which function very much like Arrays, should support the very basic list operations, like map(), filter(), and reduce(). Am I missing something?

I wrote the code for map and it seems simple enough, but it would be much better to have all the functions embedded in all the generators:

let fancyGen = g => {
  let rv = function*() {
    for (let x of g) 
      yield x;
  }
  rv.map = function*(p) {
   for (let x of g) 
      yield p(x);
  } 
  return rv;
}

I'm new to generators, so any comments on the code are welcome. In particular, is that the best way to write "the identity generator"?

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
Michael Lorton
  • 43,060
  • 26
  • 103
  • 144

1 Answers1

7

Why do generators not support map()?

Because it's too easy to fill in as a userland implementation. ES3 didn't include Array iteration methods either, maybe will see transformers for iterators in ES7 :-)

generators, which function very much like Arrays

No, please stop and distinguish iterators from generators:

  • An iterator is an object with a .next() method that conforms to the iterator protocol.
  • A generator is an iterator created by a generator function (function*). Its .next() method takes an argument which is the result of each yield inside the generator function. It also has .return() and .throw() methods.

You'll mostly be interested in iterators, where we don't pass values to next, and don't care about the end result - just like for of loops do. We can extend them with the desired methods easily:

var IteratorPrototype = Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]()));
IteratorPrototype.map = function*(f) {
    for (var x of this)
        yield f(x);
};
IteratorPrototype.filter = function*(p) {
    for (var x of this)
        if (p(x))
            yield x;
};
IteratorPrototype.scan = function*(f, acc) {
    for (var x of this)
        yield acc = f(acc, x);
    return acc;
};
IteratorPrototype.reduce = function(f, acc) {
    for (var x of this)
        acc = f(acc, x);
    return acc;
};

These should suffice for the start, and most common use cases. A proper library will extend this to generators so that values are passed through appropriately, and also will deal with the problem that iterators can be used only once before they are exhausted (in contrast to arrays).

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
  • 1
    Distinguishing generators from iterators is like distinguishing poodles from dogs -- poodles *are* dogs, although not all dogs are poodles. In a reverse of the canine case, iterators that are not generators are cranky and difficult to work with, so I am restricting my interests to generators (and to accessing through the `for..of` syntax). – Michael Lorton Jul 05 '15 at 17:11
  • 1
    It seems to me that `reduce()` should not be a generator function. – Michael Lorton Jul 05 '15 at 17:15
  • @Malvolio: In fact generators are much more cranky and difficult to work with than everyday iterators. You rather rarely come across them - all `[Symbol.iterator]` methods do return iterators, not generators. Accessing generators through `for of` doesn't use their full power. – Bergi Jul 05 '15 at 17:17