Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Composing generators

Given the following. I want to lazily apply transform to each member of the iterable returned from Object.keys.

How can I do this?

function* numbers(upto, transform) { 
  yield* Object.keys([...Array(upto)]); // How can `transform` be applied here lazily?
}

function timesTwo(n) {
    return n*2;
}

var generator = numbers(31, timesTwo)

for(var i of generator) {
    console.log(i); // 0 2 4 6 8... 60
}
like image 260
Ben Aston Avatar asked Mar 10 '23 07:03

Ben Aston


2 Answers

Since you're happy to have the transform passed into numbers, you can apply it as you generate if you take advantage of numbers being a generator:

function* numbers(upto, transform) { 
  let n = 0;
  while (n < upto) {
      yield transform(n);
      ++n;
  }
}

const timesTwo = n => n * 2;

const generator = numbers(31, timesTwo);

for (const i of generator) {
    console.log(i); // 0 2 4 6 8... 60
}

Live on Babel's REPL for those whose browsers won't run the above.


We could use your original definition of numbers, but we'd either have to apply the transform eagerly instead of lazily, or we'd have to use the array's iterator (the array will be created all at once regardless). Here's that latter one:

function* numbers(upto, transform) { 
  for (const n of Object.keys([...Array(upto)])) {
    yield transform(n);
  }
}

const timesTwo = n => n * 2;

const generator = numbers(31, timesTwo);

for (const i of generator) {
    console.log(i); // 0 2 4 6 8... 60
}

Live on Babel's REPL.


We could separate out the two aspects of numbers there and have a general-purpose transform function that is basically the generator version of map:

function* transform(iterable, f) {
  for (const v of iterable) {
    yield f(v);
  }
}

Then we can use that on a more basic numbers:

function* transform(iterable, f) {
  for (const v of iterable) {
    yield f(v);
  }
}

function* numbers(upto) { 
  yield* Object.keys([...Array(upto)]);
}

const timesTwo = n => n * 2;

const generator = transform(numbers(31), timesTwo);

for (const i of generator) {
    console.log(i); // 0 2 4 6 8... 60
}

On Babel's REPL


Side note: I'm sure you know this, but for any lurkers, the numbers in the question [and a couple of them below] iterates over a series of strings: "0", "1", etc. But then when we multiply with them, they get coerced to numbers. To actually have a series of numbers based on the question's numbers approach, we'd need

yield* Object.keys([...Array(upto)]).map(Number));
like image 188
T.J. Crowder Avatar answered Mar 14 '23 18:03

T.J. Crowder


function lazy(f) {
    return function*(iter) {
        for(const v of iter) {
            yield f(v);
        }
    }
}

function* numbers(upto, transform) { 
    yield* lazy(transform)(Object.keys([...Array(upto)]));
}

function timesTwo(n) {
    console.log('times two called on ', n);
    return n*2;
}

var generator = numbers(11, timesTwo)

for(var i of generator) {
    console.log(i); // 0 2 4 6 8... 20
}
like image 20
Ben Aston Avatar answered Mar 14 '23 18:03

Ben Aston