Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to extend the Generator class?

I tried to filter a generator and had the expectation that this kind of general functionality must be defined anywhere in JavaScript, because it is defined for Arrays, but I can not find it. So I tried to define it. But I can not extend the built-in generators.

I have an example generator

function make_nums ()
{
  let nums = {};
  nums[Symbol.iterator] = function* () {
    yield 1;
    yield 2;
    yield 3;
  };
  return nums;
}

generating some numbers.

[...make_nums()] // => Array [ 1, 2, 3 ]

If I build an array, I can filter the array by the use of the filter function for arrays.

[...make_nums()].filter(n => n > 1) // => Array [ 2, 3 ]

But I do not want to build an array. Instead I want to take the old generator and build a new filtering generator. For this I wrote the following function.

function filtered (generator, filter)
{
  let g = {};
  g[Symbol.iterator] = function* () {
    for (let value of generator)
      if (filter(value))
        yield value;
  };
  return g;
}

which can be used to do what I want.

[...filtered (make_nums(), n => n > 1)] // => Array [ 2, 3 ]

But this is a very general function, which can be applied to every generator in the same way the filter function can be applied to every Array. So I tried to extend generators in general, but I do not understand how.

The MDN documentation for generators suggests somehow that Generator.prototype may exist, but it does not seem to exist. When I try to define something in Generator.prototype, I get the error

ReferenceError: Generator is not defined

How can I extend the built-in Generator class?

like image 480
ceving Avatar asked Nov 28 '17 14:11

ceving


2 Answers

With the traditional caveat that extending built-in prototypes is not necessarily the best idea, and that it's something to be done with caution, you can get the generator function prototype with

const genproto = Object.getPrototypeOf(function*(){});

With that you could add a filter() capability:

Object.defineProperty(genproto, "filter", {
  value: function*(predicate) {
    for (let value of this())
      if (predicate(value)) yield value;
  }
});

And thus:

console.log([... function*() {
    for (let i = 0; i < 10; i++) yield i;
  }.filter(value => value % 2 === 0)
]);

will print [0, 2, 4, 6, 8].

To be clear: this answer is about extending the prototype for generator functions, not the generator objects themselves. Thus this will ensure that every generator function in the program can use that .filter() method and any other similar extension.

like image 148
Pointy Avatar answered Nov 15 '22 12:11

Pointy


It turned out that I was confused about Generator and Iterable. I thought I had to extend Generator, but it is actually sufficient to extend Iterable. And the fact that Iterable does not seem to be defined by JavaScript either, makes it easy to define it, to avoid problems with modifications of built-in prototypes.

This actually does what I tried to achieve.

class Iterable {
  constructor (generator) {
    this[Symbol.iterator] = generator;
  }
}

Iterable.prototype.filter = function (predicate) {
  let iterable = this;
  return new Iterable (function* () {
    for (let value of iterable)
      if (predicate (value))
        yield value;
  });
};

I can create Iterables

make_nums = new Iterable(function* () { yield 1; yield 2; yield 3; });

can use them

[...make_nums] // => Array [ 1, 2, 3 ]

and can filter them

[...make_nums.filter(n => n > 1)] // => Array [ 2, 3 ]
like image 5
ceving Avatar answered Nov 15 '22 12:11

ceving