How would we copy an iterator?

# Philip Polkovnikov (10 years ago)

As pretty everyone already seen, generators are usually used in composition with promises to yield (pun intended) a plain simple linear code. This solution looks very much like monads from Haskell, yet in a better syntax: every a <- b in Haskell is a = yield b in JS, and that aids to get rid of several useless lines of code.

Actually, one could easily implement other monads over Haskell, changing the structure of the data passed to yield. Generators + promises are almost a Cont monad (continuations). But here's an issue.

Let's take a look at the List monad (nondeterminism), specifically how we would use one in JS:

function* doesntMatter() {
	 var a = yield [1, 2, 3];
	 var b = yield [1, 2, 3];
	 return a + b;
}

Now, when we run(doesntMatter)(), we should get [2, 3, 4, 3, 4, 5, 4, 5, 6]. But there's a problem with implementation: we would like to run the rest of the computation for every item in an array passed to yield, but we can't! There's no way one could copy the current state of an iterator.

The same happens to our original generator + promise usage. We could use the same syntax to work with events (i.e. repetitive callback calls), not just series of nested callbacks, but we don't have that vital iter.copy() method.

Obviously, mentioning Haskell in this message was overkill, and that could be a reason for someone to diasgree that we really need such feature. But, as I previously described, it's quite a natural thing that is missing, and it doesn't have too much in common with Haskell.

What is the right way to put this thing into discussion/standardization process? What do you think about it?

# François REMY (10 years ago)

For the record, I proposed this feature before, and it wasn't seen as a good idea, for some reason: esdiscuss.org/topic/function-is-not-mandatory#content-47

Given how close ES6 is to ship, I think it's too late to make such changes to the spec, we will have to live with it. Use Traceur to generate state machines from your "yield functions" and use that as a basis in your code.

Best , François

# Philip Polkovnikov (10 years ago)
  1. Where is the function run?

The function run is pretty classic variation on the code you would see in Q or bluebird.

function run(gen) {
	return function (/* args */) {
		var args = Array.prototype.slice.call(arguments);
		var iter = gen.apply(args);
		(function step(arr) {
			arr.forEach(function (item) {
				iter.copy().next(item);
			});
		})(iter.next());
	};
}

Probably, it would be nice to work out return there, so that this could be a full-fledged nondeterminism monad, but everything should be already clear.

  1. I am against copying a generator instance. It's just like cloning a context, with many difficulties and ambiguity. Shall we deep-clone or not? How to efficiently implement such a method? I don't see useful cases in which we need to clone a generator instance.

Regarding the availability of .copy() you can read in the description of one Python package I've just found:

www.fiber-space.de/generator_tools/doc/generator_tools.html

Given how close ES6 is to ship, I think it's too late to make such changes to the spec, we will have to live with it. Use Traceur to generate state machines from your "yield functions" and use that as a basis in your code.

The copy is worth the problems of frame copying. Anyway, I understand that ES6 won't have copy as standard is just to be released, but it should be added to JS once.

# Philip Polkovnikov (10 years ago)
    1. Where is the function run?

The function run is pretty classic variation on the code you would see in Q or bluebird.

function run(gen) { return function (/* args */) { var args = Array.prototype.slice.call(arguments); var iter = gen.apply(args); (function step(arr) { arr.forEach(function (item) { iter.copy().next(item); }); })(iter.next()); }; }

Probably, it would be nice to work out return there, so that this could be a full-fledged nondeterminism monad, but everything should be already clear.

    1. I am against copying a generator instance. It's just like cloning a context, with many difficulties and ambiguity. Shall we deep-clone or not? How to efficiently implement such a method? I don't see useful cases in which we need to clone a generator instance.

Regarding the availability of .copy() you can read in the description of one Python package I've just found:

www.fiber-space.de/generator_tools/doc/generator_tools.html

  • Given how close ES6 is to ship, I think it's too late to make such changes to the spec, we will have to live with it. Use Traceur to generate state machines from your "yield functions" and use that as a basis in your code.

The copy is worth the problems of frame copying. Anyway, I understand that ES6 won't have copy as standard is just to be released, but it should be added to JS once.

2015-02-13 21:35 GMT+03:00 François REMY <francois.remy.dev at outlook.com>:

# Salvador de la Puente González (10 years ago)

Hey Philip,

you can see esdiscuss.org/topic/proposal-generator-clone-and-generator-goto for more about this topic.

# Brendan Eich (10 years ago)

As the comments from Tab Atkins and John Lenz there suggest, this is best done above the level of the standard, for now. If we need to roll up itertools including tee in ES7 (I am pretty sure we do), the best way to get there is via github -- not by prematurely standardizing APIs or over-extending language kernel semantics in TC39.

# Salvador de la Puente González (10 years ago)

Yep. This was already discussed in the topic I mentioned before. Just to remember, the real problem with tee() is that the generators are not actually independent as you can not .send() different information to each one to make them diverge.

So producing a real clone of the generator is not possible from "above the standard".

Hope it helps.

# Brendan Eich (10 years ago)

Salvador de la Puente González wrote:

Yep. This was already discussed in the topic I mentioned before. Just to remember, the real problem with tee() is that the generators are not actually independent as you can not .send() different information to each one to make them diverge.

Why not? The generator would switch on sent value, in a loop.

We're deep in the Turing tarpit, where anything can be coded on top of generators. Arguing for just a bit more kernel semantics needs justification, like the cost to library code is too high (by some concrete measure), or the cliché is so popular it's time to absorb into a future JS standard.

# Benjamin (Inglor) Gruenbaum (10 years ago)

Why not? The generator would switch on sent value, in a loop.

Well, let's say we have an iterator that does nothing once, returns what was sent to it the first 5 times and then is done:

function* gen(){
  var res = yield;
  for(var i = 0; i < 5; i++){
    res = yield res;
  }
}

var iter = gen(), res = {}, i = 1;
iter.next();
do {
  res = iter.next(i++);
  console.log(res.value);
} while(!res.done);

How would you clone that with a switch?

# Brendan Eich (10 years ago)

The switch has to go on the inside -- the generator author has to cooperate with client code to make something that can be "cloned".

It could be this is too great a hardship, but we need more evidence, not contrived examples.

# Benjamin (Inglor) Gruenbaum (10 years ago)

Of course this example is contrived! It's the simplest example I could think of. I do think it stands for every example of any iterator that takes any input from .send or .throw. I have plenty of other contrived examples (implementing fork like this by using generators for coroutines with promises is at least mildly cool).

That said I absolutely agree that more practical examples are required in order to make this already way-powerful two way protocol into something even more complicated.

# Brendan Eich (10 years ago)

Benjamin (Inglor) Gruenbaum wrote:

Of course this example is contrived! It's the simplest example I could think of. I do think it stands for every example of any iterator that takes any input from .send or .throw. I have plenty of other contrived examples (implementing fork like this by using generators for coroutines with promises is at least mildly cool).

I <3 fork, Unix kid here. Still, agree here:

That said I absolutely agree that more practical examples are required in order to make this already way-powerful two way protocol into something even more complicated.

Another consideration beyond just the needs-justification language power-up -- the implementation burden goes up too. You're talking about cloning an activation. Easy in the unoptimized world of generators today, not so much in the future.

It may be worth it, but we need to feel the "back-pressure" from developers using generators, first.

Can developers use conventions and APIs to implement clone today? I.e.,

Object.defineProperty(function*(){}.prototype.__proto__, 'clone', 
{value: XXX, writable: true, configurable: true, enumerable: false});

What would have to go in the XXX that's self-hostable, and how would generators that participate have to be written?

# Salvador de la Puente González (10 years ago)

El 23/02/2015 20:29, "Brendan Eich" <brendan at mozilla.org> escribió:

What would have to go in the XXX that's self-hostable, and how would generators that participate have to be written?

This is what I was trying to find. Some way of copying without altering client function.

Let's see.

# Tab Atkins Jr. (10 years ago)

On Mon, Feb 23, 2015 at 10:34 AM, Brendan Eich <brendan at mozilla.org> wrote:

Why not? The generator would switch on sent value, in a loop.

.tee() "clones" an iterator by caching the values returned by the frontmost iterator, and returning the cached values to the lagging iterators rather than running the master iterator itself. So Salvador is right - you can't .send() different values to a lagging iterator, because the iterator only runs once.

To do more sophisticated "cloning", the generator has to cooperate with client code, as you say. It has to be able to serialize out its state at any given yield point, so it can jump-start a fresh "clone" at an arbitrary point and return that, rather than the "fake" cloning that .tee() does.

(Not saying anything needs to be added right now. Just pointing out the specifics, as it seemed you were talking past each other.)

# Brendan Eich (10 years ago)

Tab Atkins Jr. wrote:

(Not saying anything needs to be added right now. Just pointing out the specifics, as it seemed you were talking past each other.)

Thanks, I was thinking beyond tee as a generic, which copies (requiring a set-aside, could be a lot of memory). If the cliff leads to compilation tarpits, it could still be informative to see the winners emerge from the muck, before we build in some kind of generator clone method.