How would we copy an iterator?
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
- 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.
- 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.
-
- Where is the function
run
?
- Where is the function
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.
-
- 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>:
Hey Philip,
you can see esdiscuss.org/topic/proposal-generator-clone-and-generator-goto for more about this topic.
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.
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.
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.
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?
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.
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.
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 (implementingfork
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?
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.
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.)
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.
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 isa = 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 aCont
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: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 toyield
, 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?