Reason why generators do not have references to themselves?
When writing a generator as, for example, an instance method of a class, it would be very surprising for its 'this' to be bound to something other than the class' instance.
arguments.callee.caller would have done that </trolling> </but-actually-not-so-much>
Andrea Giammarchi wrote:
arguments.callee.caller would have done that </trolling> </but-actually-not-so-much>
No, Bradley wants the generator-iterator (what ES6 draft calls a Generator), not the generator function (GeneratorFunction). Any .callee would have to be a function, so a GeneratorFunction.
But the gist'ed example won't work:
function* doLogin_() {
try {
/* START ASYNC TRANSPILE */
login(req).then(function (session) {
gen.next(session);
You can't next an active generator.
Instead, the trick is to avoid promises inside the generator function. Put them "on the outside" (and "backstage") as task.js does. taskjs.org.
Is that true even though then should fire after the generator unwinds its stack? I am using regenerator right now while playing with this stuff.
Bradley Meck wrote:
Is that true even though then should fire after the generator unwinds its stack?
True if .then always runs in a later turn -- sorry.
Still, my point stands: you are not using task.js-like scheduler/combinator approach for an apples-to-apples comparison with await. If you do, then we're down to the obligation of a task.js download, and some syntactic sugar.
// assume req in scope
spawn(function*() {
try {
var session = yieldlogin(req); // wait until the promise resolves
gotoProfile(session);
}
catch (e) {
printError(e);
gotoLogin();
}
// implicit return undefined
});
BTW, in ES7, no "use await"; will be needed ;-).
If you want to control when the task starts, you'd use new Task. If you want to send req, you'd control starting via t = new Task(function*(){...}) and call t.next(req). Hope this is all clear from taskjs.org and the code.
I am using regenerator right now while playing with this stuff.
Cool -- can you try task.js?
Task.js is still on "JavaScript 1.8," and is not ES6-compatible. It won't work with modern browsers, or with Regenerator.
Fork and fix, should be easy. I expect a PR would be accepted in due course. SpiderMonkey seems to have ES6 generator support somewhat there (thanks to Andy Wingo), should be all there soon enough.
On Jan 23, 2014, at 2:14 PM, Bradley Meck wrote:
I was playing around with generators / looking at await for promises and notices a very difficult thing revolving around generators not having a reference to themselves.
See:
gist.github.com/bmeck/72a0f4f448f20cf00f8c
I have to end up wrapping the generator function to get a reference to the generator for passing to nested functions.
Is there a reason the execution context is not a generator / there is no reference back to the generator during [[call]]?
The [[Call]] whose execution context is suspended and captured is by the generator instance is the actual call to the generator function that created that instance. So, the this value of that context is the this value of that original call of the generator function. Creation and initialization of the generator instance takes place before executing the generator function body. This means there are some interesting ways that the this value manifests depend upon the nature of the this call.
For example, (this is what the ES6 spec. says should happen, I don't know if any implementation has this all correct yet)
Assume:
function g* () {"use strict"; yield "this: " + this}; //don't want undefined this turning into the global object
g.prototype = function toString() {return "a generator instances made by g"};
var obj = {
g,
toString() {return "obj"}
};
//plan function call
console.log(g().next().value); //"this: undefined"
//same as
console.log(g.call(undefined).next().value);
//Just like calling any function call, undefined is the default this value.
//method call
console.log(obj.g().next().value); //"this: obj"
//same as:
console.log(g.call(obj).next().value);
//just like any method call
//explicit constructor call
console.log((new g()).next().value): //"this: a generator instances made by g"
//same as:
console.log(g.call(g[Symbol.create]()).next().value);
//just like newing any constructor function
In other words, if you want this bound to the function instance within the body of the constructor function use the generator function as a constructor and new it.
How, does it all work? It depends upon the generator function being to distinguish an uninitialized generator instance from other objects. This is something that all constructors that want to have the same behavior for both new and regular calls need to be able to do.
The prolog of a generator function (before capturing and suspending the execution context) looks at the this value that was passed to it. If the this value is an uninitialized generator object (such as is returned by the @@create method of a generator function) it initializes that generator object, captures the execution context, and return the now initialized generator object that was passed in as its this.
If the this value is anything else (undefined, any object that isn't an uninitialized generator instance) the prolog allocates a new generator instance (using @@create), initializes it, captures the context and returns the newly allocated generator instance.
In neither case is the original this binding modified.
Still, my point stands: you are not using task.js-like
scheduler/combinator approach for an apples-to-apples comparison with await. If you do, then we're down to the obligation of a task.js download, and some syntactic sugar.
see comments in gist, adding a library also has some stuff with source maps that I am not too keen on in particular. I can use libraries to simplify many things / manage code, but doing similar tasks without using a library seems more in line w/ my original question (which has been answered). Either way, for now it seems like generator-iterators won't have references to themselves and I can just wrap it.
Allan: an interesting idea that does simplify things, updating gist. Still using a wrapper since even w/ new the generator returns a generator instance (otherwise I could use instanceof checks).
Note: cleaned up gist somewhat in general / simplified
On 1/23/2014 4:46 PM, Domenic Denicola wrote:
Task.js is still on "JavaScript 1.8," and is not ES6-compatible. It won't work with modern browsers, or with Regenerator.
For most uses, Task.js's Task.spawn can be replaced with a smaller helper function:
function spawn(thunk) {
return new Promise(function(resolve, reject) {
var gen = thunk();
function _next(v) {
return handle(gen.next(v));
}
function _throw(e) {
return handle(gen.throw(e));
}
function handle(r) {
return r.done ? r.value : Promise.cast(r.value).then(_next, _throw);
}
Promise.cast(_next()).then(resolve, reject);
});
}
On Jan 23, 2014, at 4:49 PM, Brendan Eich <brendan at mozilla.com> wrote:
Fork and fix, should be easy. I expect a PR would be accepted in due course. SpiderMonkey seems to have ES6 generator support somewhat there (thanks to Andy Wingo), should be all there soon enough.
Working on it lately, actually. And using regenerator for the tests (\o/)! Also drastically simplifying the library since the customizable scheduler stuff, while kind of neat, is probably less compelling than a minimal library. Down to under 150 lines, unminified with comments, and still shrinking...
Taking note with async polyfills and syntax for a minute. I spent a fair amount of time actually writing the spec of my intent/goal after getting some thought and working things through in my head (varies slightly from old 2011 await):
gist.github.com/bmeck/674e21f40fe6e9ce6304#file-close-but-no-cigar-sweet-js-L6
I can get really close with sweet.js (see gist), but as such I still have to use new when invoking the generator function which feels dirty in my mind. Perhaps if I did some crazy code transforms to test if the generator function was using await it would work. That would require me to wrap it similar to how task.js and all the other libraries IRC bombarded me with. I think it very strange that they all:
- make a promise that will be returned (finalPromise)
- call the generator and hook it up to the promise
- don't appear to have a clean way to make the generator continue
without resolving finalPromise and without forcing the generator to
completion
- bluebird's Promise.coroutine comes closeish but still needs to run the generator to completion
It seems that every approach I am taking gets me slightly closer, but none are a clean way for a generator to iterate itself without some hacky argument passing and wrapping.
PS. Sorry about the horrifying sweet.js macro, but it is functional enough in Chrome with experimental JS from what I tried out.
It seems like this is largely a matter of personal aesthetics ("feels dirty," "hacky," etc.) but nothing is wrong or even hard functionally. Furthermore given the prevalence of code out there that uses such "hacks" without compunction, it seems that your aesthetics are not shared by most.
Maybe the easiest path toward resolving this, um, problem?, is simply adjusting your preferences to recognize this kind of code for the beautiful code it is.
Perhaps, but I am still a bit concerned functionality wise that I do not
have a clean way to force the new generator()
piece of code to be inside
of the generator.
I cannot easily convert it akin to how new can be caught:
function* generatorFn() {
if (this instanceof generatorFn) {
return new generatorFn // does not work
}
}
Since it will be wrapped.
So I guess a throw trap is the only way:
function* generatorFn() {
if (this instanceof generatorFn) {
throw new Error("This must be called with new");
}
}
But even then we are not able to tell if the generator is in
"standbyStartup" or the currently active generatorFn
. Does the lack of
being able to test that make sense?
Bradley Meck wrote:
Perhaps, but I am still a bit concerned functionality wise that I do not have a clean way to force the
new generator()
piece of code to be inside of the generator.
I think you've gone down a bad path. Even without task.js, couldn't you put the promises .then'ing in the generator schedule, instead of requiring each generator to open-code the .then/.catches, which in turn requires you to have a ref to the generator?
Le 24 janv. 2014 à 10:06, Bradley Meck <bradley.meck at gmail.com> a écrit :
(...) I still have to use new when invoking the generator function which feels dirty in my mind.
Interestingly, using new
when invoking a generator function feels cleaner in my mind. :-) In short (and since it is somewhat off-topic), new myGenerator
suggests the sensible idea of getting a new iterator each time the expression is evaluated, while myGenerator()
suggests nothing more that may be encoded in the name of the generator function.
[replying to a message you sent off-list, hope it is ok. /be]
Brendan Eich wrote:
Bradley Meck wrote:
Perhaps, but I am still a bit concerned functionality wise that I do not have a clean way to force the
new generator()
piece of code to be inside of the generator.I think you've gone down a bad path. Even without task.js, couldn't you put the promises .then'ing in the generator schedule,
Sorry, last word should be "scheduler" above.
The await syntax needs a scheduler behind the scenes, and task.js has one. The scheduler duck-types the result of yield expressions in the generator, and if it is a promise, arranges to .next the generator when the promise is fulfilled, or .throw at it when rejected.
Instead you compile await in a way that expands the promise .then'ing inline in the generator body, requiring a ref to the generator in a closure, for use from function expressions passed to .then and .catch.
Is this more clear?
I'd like to suggest another sense in which you may have gone down a bad path: you're assuming that await is paired with function*, but it could instead be (like C#) paired with its own async-function syntactic form. Let's say for the sake of argument that async is just a keyword that takes a function form:
async function sizeOfZombocom() {
let all = await download("http://zombo.com");
return all.length;
}
Imagine we were designing this with a macro system like sweet.js. The temptation is for async
to unhygienically bind await
to a macro within its body, but Racket has developed a cleaner mechanism for thinking about paired forms like this, which they call "syntax parameters" (kind of a confusingly generic sounding name) 1, 2. The basic idea is that you bind both async
and await
at the same time, so await
is always bound, even outside of an async function, but the two collaborate so that async
informs await
of its syntactic context. So it would look something like this:
Example 1:
import { async, await } from "async";
await 1; // syntax error: await used outside of async function
Example 2:
import { async, await } from "async";
function* sizeOfZombocom() {
let all = await download("http://zombo.com"); // syntax error: await used outside of async function
return all.length;
}
Example 3:
import { async, await } from "async";
async function sizeOfZombocom() {
let all = await download("http://zombo.com"); // great success
return all.length;
}
This makes your abstraction airtight: await
is a concept that belongs to async
functions, not generator functions; generator functions are merely the internal implementation technique.
Currently, sweet.js doesn't have syntax parameters, but it'd be a good experiment to add them them and try implementing async/await as I've sketched here.
On Jan 27, 2014 2:09 PM, "David Herman" <dherman at mozilla.com> wrote:
I'd like to suggest another sense in which you may have gone down a bad path: you're assuming that await is paired with function*, but it could instead be (like C#) paired with its own async-function syntactic form. Let's say for the sake of argument that async is just a keyword that takes a function form:
async function sizeOfZombocom() { let all = await download("http://zombo.com"); return all.length; }
This is similar to way we implemented async in Traceur a few years ago (time flies). A lot of code can be be shared between generators and async functions but you do not want to implement async using generators.
Could not get the example deferred function to work with traceur from the wiki page to see what it generates. google/traceur-compiler/wiki/LanguageFeatures
Been talking off list since this is off topic, but keeps coming up. I ended up following Brendan's advice. I have a sweet.js that works well, but still requires a wrapper.
gist.github.com/bmeck/5146e40d71bbd57ec9ec
Tests seem to pass, yielding promises without awaiting, direct eval (without regenerator), usage in regenerator, etc.
My macro does no match the semantics of what is in the wiki, but would be interested in how traceur does function returns from deferred functions / if I can use yield.
Unsure on how new syntax features like await should deal w/ multiple operands though, like if await wanted to turn multiple promises into an array. Comma operator takes comma separated list out of the equation.
Bradley Meck wrote:
Unsure on how new syntax features like await should deal w/ multiple operands though, like if await wanted to turn multiple promises into an array. Comma operator takes comma separated list out of the equation.
No one proposed this. Do you have a use-case? Task.js and others provide combinators such as choose and join, this seems better (compositional, no magic/weird reinterpretation of comma).
This is more just a comment / thinking out loud about syntax. Not related to a specific use case. The following is just what was starting to bring my thoughts around to that though.
I am starting to build things with the constructs in this thread, so building out things in a similar manner to Promise.[race|all] . Even with the built ins, some things would just be interesting to have on hand:
I went the same route and made a: mapPromises(Map<?,Promise<?>>)=>{rejections:Map<?,?>,resolutions:Map<?:?>}, while working out some example problems / speccing out a User lobby system.
I was trying to also figure out a way to dynamically add / remove Promises while waiting on full resolution. Promises.all is useful, but when working out people joining / removing themselves in a voting system it is a bit tough. Ended up making my own function / plumbing for that: mapDynamicallSpecifiedPromises => {promise:Promise<Map<?:?>>,setMappings(Map<?,Promise<?>>)}. This is probably too much for stdlib though. Being able to dynamically change the mappings makes for a much easier time if you have a constantly changing list of users in a room for example.
I was playing around with generators / looking at await for promises and notices a very difficult thing revolving around generators not having a reference to themselves.
See:
gist.github.com/bmeck/72a0f4f448f20cf00f8c
I have to end up wrapping the generator function to get a reference to the generator for passing to nested functions.
Is there a reason the execution context is not a generator / there is no reference back to the generator during [[call]]?