Gunther Schadow
Gunther Schadow

Reputation: 1739

What is the official way to add methods to the Generator.prototype?

I am totally new to javascript generators. I have a lot of practical experience with Java Iterators and have done all softs of stuff with chains of Iterators in Java. But I am only just now discovering the generator function and yield and even yield* in javascript. I want to add a map and a reduce function somehow. Let's begin with map.

Yes, I have looked at wu.js, but to me that looks too much like a hack. Not opposed to re-wrapping in general, but I have the hunch that it could be doable more directly.

Here is what I have, first a range generator function:

function* range(start = 0, end = Infinity, step = 1) { 
    for(let i = start; Math.sign(step)*(end - i) > 0; i += step)
        yield i;
}

[...range(8,0,-1)] // > [8, 7, 6, 5, 4, 3, 2, 1]

That's cool. Now how can I add my map function? I discovered this:

range.prototype > Generator {}

It looks like this Generator {} is inviting me to stick some methods on it! So I can add map on that range prototype:

range.prototype.map = function*(fn) { 
    for(let x of this)
        yield fn(x);
}

[...range(8,0,-1).map(x => 2**x)] // > [256, 128, 64, 32, 16, 8, 4, 2]

Wow! But if I want to follow a map with another map I need to say this first:

range.prototype.map.prototype.map = range.prototype.map
[...range(8,0,-1).map(x => 2**x).map(x => x-1)] // > [255, 127, 63, 31, 15, 7, 3, 1]

But I'd like to stick map on all Generators. When Array functions were not yet available on IE dinosaur edition 8, I did this:

if(!Array.prototype.map)
    Array.prototype.map = function(fn) { 
        var r = []; 
        for(var i = 0; i < this.length; i++) 
            r.push(fn(this[i], i, this);
    }

and that allowed me to work in IE dinosaur edition 8 just like in any decent browser. No quirky Util.arrayMap(...) function required. So I feel tempted to do just this now:

if(!Generator.prototype.map)
    Generator.prototype.map = = function*(fn) { 
        for(let x of this)
            yield fn(x);
    }

But uh oh, Generator is not a globally defined name like Array is! So I sneak in:

range.prototype.__proto__.map = function*(fn) { 
    for(let x of this) {
      yield fn(x);
    }
}

[...range(8,0,-1).map(x => 2**x).map(x => x-1)] // > [255, 127, 63, 31, 15, 7, 3, 1]

BINGO! I did it. To avoid using __proto__ I can also do:

Object.getPrototypeOf(range.prototype).map = ...

to the same effect.

But isn't there some more official way than to sneak through the side entrance with this range.prototype._proto_ trick?

By more official way I mean say something straight forward such as

xxx.Generator.prototype.map = ...

where xxx is whatever module or some such the Generator class is defined in?

Upvotes: 0

Views: 171

Answers (1)

trincot
trincot

Reputation: 350996

I want to add a map and a reduce function somehow.

As of ECMAScript 2025 these are available as iterator helper methods, so you don't need to create them anymore. The iterator objects returned by native functions are iterator helper objects which inherit these methods; this is also true for generators:

// Your generator function
function* range(start=0, end=Infinity, step=1) { 
    for (let i = start; Math.sign(step) * (end - i) > 0; i += step)
        yield i;
}

// Your example adapted to make use of iterator helper methods:
const result = range(8, 0, -1)
               .map(x => 2**x)
               .map(x => x-1)
               .toArray();
               
console.log(result); // > [255, 127, 63, 31, 15, 7, 3, 1]

Note that you can use spread syntax to get the array, but I'm a fan of chaining the toArray() method (which is also an iterator helper method). It just gives it a more elegant look, in my opinion.

Adding other methods

Although we now have native map and filter iterator helper methods (and a few other!), you could still want to add more. You can do so on the Iterator.prototype object (although many would argue that adding to a native prototype object is not advised). Here is a demo of a takeWhile method, applied to your example:

// Your generator function
function* range(start=0, end=Infinity, step=1) { 
    for (let i = start; Math.sign(step) * (end - i) > 0; i += step)
        yield i;
}

// Add a method to the prototype
Iterator.prototype.takeWhile = function* (condition) {
    for (const value of Iterator.from(this)) {
        if (!condition(value)) break;
        yield value;
    }
}

const result = range(8, 0, -1)
              .map(x => 2**x)
              .takeWhile(x => x > 100)
              .toArray();
              
console.log(result); // [256, 128]

Upvotes: 0

Related Questions