I'm trying to create a List monad in ES6 using generators. To make it work I need to create a copy of an iterator that has already consumed several states. How do I clone an iterator in ES6?

function* test() {
    yield 1;
    yield 2;
    yield 3;
}

var x = test();
console.log(x.next().value); // 1
var y = clone(x);
console.log(x.next().value); // 2
console.log(y.next().value); // 2 (sic)

I've tried clone and cloneDeep from lodash, but they were of no use. Iterators that are returned in this way are native functions and keep their state internally, so it seems there's no way to do it with own JS code.


Iterators […] keep their state internally, so it seems there's no way

Yes, and that for a good reason. You cannot clone the state, or otherwise you could tamper too much with the generator.

It might be possible however to create a second iterator that runs alongside of the first one, by memorizing its sequence and yielding it later again. However, there should be only one iterator that really drives the generator - otherwise, which of your clones would be allowed to send next() arguments?


I wrote a do-notation library for JavaScript, burrido. To get around the mutable generator problem I made immutagen, which emulates an immutable generator by maintaining a history of input values and replaying them to clone the generator at any particular state.


You can't clone a generator--it's just a function with no state. What could have state, and therefore what could be cloned, is the iterator resulting from invoking the generator function.

This approach caches intermediate results, so that cloned iterators can access them if necessary until they "catch up". It returns an object which is both an iterator and an iterable, so you can either call next on it or for...of over it. Any iterator may be passed in, so you could in theory have cloned iterators over an array by passing in array.values(). Whichever clone calls next first at a given point in the iteration will have the argument passed to next, if any, reflected in the value of the yield in the underlying generator.

function clonableIterator(it) {
  var vals = [];

  return function make(n) {
    return {
      next(arg) {
        const len = vals.length;
        if (n >= len) vals[len] = it.next(arg);
        return vals[n++];
      },
      clone()   { return make(n); },
      throw(e)  { if (it.throw) it.throw(e); },
      return(v) { if (it.return) it.return(v); },
      [Symbol.iterator]() { return this; }
    };
  }(0);
}

function *gen() {
  yield 1;
  yield 2;
  yield 3;
}

var it = clonableIterator(gen());

console.log(it.next());
var clone = it.clone();
console.log(clone.next());
console.log(it.next());

Obviously this approach has the problem that it keeps the entire history of the iterator. One optimization would be to keep a WeakMap of all the cloned iterators and how far they have progressed, and then clean up the history to eliminate all the past values that have already been consumed by all clones.


You could do something like is provided in Python itertools.tee, i.e. let a function return multiple iterators that take off from where the given iterator is at.

Once you call tee, you should no longer touch the original iterator, since tee is now managing it. But you can continue with the 2 or more "copies" you got back from it, which will have their independent iterations.

Here is how that function tee can be defined, with a simple example use of it:

function tee(iter, length=2) {
    const buffers = Array.from({length}, () => []);
    return buffers.map(function* makeIter(buffer) {
        while (true) {
            if (buffer.length == 0) {
                let result = iter.next();
                for (let buffer of buffers) {
                    buffer.push(result);
                }
            }
            if (buffer[0].done) return;
            yield buffer.shift().value;
        }
    });
}

// Demo
function* naturalNumbers() {
    let i = 0;
    while (true) yield ++i;
}

let iter = naturalNumbers();

console.log(iter.next().value); // 1
console.log(iter.next().value); // 2

let saved;
[iter, saved] = tee(iter);

console.log("Saved. Continuing...");
console.log(iter.next().value); // 3
console.log(iter.next().value); // 4

console.log("Restored");

iter = saved;

console.log(iter.next().value); // 3
console.log(iter.next().value); // 4
console.log(iter.next().value); // 5