-
Notifications
You must be signed in to change notification settings - Fork 1.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ES6 Generators #534
Closed
Closed
ES6 Generators #534
+503
−79
Commits on Aug 6, 2015
-
Behavior: Generators are iterable. Each value yielded from the generator function is iterated. Generators are iterators. Values can be passed in through the `next` method. The iterator result values will be either a) the yielded values, b) the value returned from the generator (the completion value) or c) `undefined`, if `next` is called after completion. Generators can delegate to other iterables, including other generators. Values passed to `next` on the delegator generator will be passed through to the delegatee. Similarly, values yielded by the delegatee will be returned along the iterable/iterator interfaces. The "delegate yield" expression, `yield *`, returns the completion value of the delegatee. Implementation: While normal functions' return type is the type of the `return` statement, generators are different. The return type is always a Generator, where the second type argument is the type of the `return` statement. We use the same process to infer this type--an internal tvar named "return"--but then override the return type when creating the function type, if the function is a generator. In order to track the `next` and `yield` types, we introduce new internal tvars, which accumulate lower bounds according to their use, and upper bounds from explicit annotation of the Generator type. Caveats: The generator interface is highly dynamic, and not particularly amenable to static typing. The type of Generator<Y,R,N> enforces that all next values and all yielded values be described by a single type, but it's quite normal to use values of different types. Consider the following example: ```javascript function *userGen() { var name = yield("What is your name?"); var age = yield("How old are you?"); return { name, age } } var gen = userGen(); var name, age, user; if (name = prompt(gen.next().value)) { if (age = prompt(gen.next(name).value)) { user = gen.next(parseInt(age, 10)).value; // user is { name: string|number, age: string|number } // but we "want" { name: string, age: number } } } ``` To escape this pitfall, you can use `Generator<any,any,any>` or write the necessary dynamic type tests. Ideally, Flow would enforce that you first pass a `string` to `next`, then a `number`, but that isn't possible in general, and in specific instances is still very hard, so I'm leaving it for future work (if ever). Another difficulty is the fact that the argument to `next` is optional. This is because, in order to "start" the generator, you need to call `next` once. Put differently, the first argument to `next` is not returned by the first `yield` expression; the second argument to `next` is. Consider the following example: ```javascript function *bmiCalc() { var height: number = yield("What is your height, in meters?"); var weight: number = yield("What is your weight, in kilograms?"); return weight / (height * height); } // The first argument to next is always `void`. The value of this // expression is the string "What is your height..." bmiCalc.next(); // This call to `next` expects the value for `height`, above, but // because the type of `next` marks its argument optional, we allow // this. :( bmiCalc.next(); ``` In this implementation, I wanted to make things strict, so the return type of a yield expression is always `Y|void`, and the generator needs to do a dynamic type test in order to get at the expected `Y`. The above caveats, and the strict interpretation of this implementation, has the consequence that most, if not all, generator code "in the wild" will result in type errors from Flow. We might consider sacrificing correctness here for practical purposes, but I think it will be easier to go from strict to less strict than the other way around.
Configuration menu - View commit details
-
Copy full SHA for c9ddfd6 - Browse repository at this point
Copy the full SHA c9ddfd6View commit details
Commits on Aug 21, 2015
-
Configuration menu - View commit details
-
Copy full SHA for 673271c - Browse repository at this point
Copy the full SHA 673271cView commit details
Commits on Sep 4, 2015
-
Configuration menu - View commit details
-
Copy full SHA for 661e9d4 - Browse repository at this point
Copy the full SHA 661e9d4View commit details -
Change IteratorResult interface to have a single type parameter
In reality, the type of IteratorResult is something like: ``` type IteratorResult<Y,R> = { done: false, value: Y } | { done: true, value: ?R } ``` However, boolean sentinels is not implemented. It's simpler for everyone to use the single-parameter interface, so let's keep things simple.
Configuration menu - View commit details
-
Copy full SHA for 898851a - Browse repository at this point
Copy the full SHA 898851aView commit details -
Configuration menu - View commit details
-
Copy full SHA for 5706251 - Browse repository at this point
Copy the full SHA 5706251View commit details
Commits on Sep 5, 2015
-
Implement class method generators
Class methods go through a quite different, and a bit more convoluted process. The hack I was using for function declarations where I swapped out the inferred return type with a Generator typeapp no longer applies. This is a good thing, because it forced me to replace the hack with something better. Like async functions, the fancy generator return type is established in the Return statement handler directly. If there is no explicit return, we default to a Generator typeapp where R=void. I intentionally left the capacity for async=true/generator=true var scopes. Babel, via regenerator[1], seems to understand `async function*` using the AsyncIterator[2] proposal. For now, the parser will reject async generator functions, but we might choose to support async iterators in the future, so having the capacity to do so in VarScope seems right. 1. https://github.com/facebook/regenerator/blob/9423b4ade99173b603430a151599cf8637166f16/test/async.es6.js#L259 2. https://github.com/zenparsing/async-iteration/#async-generator-functions
Configuration menu - View commit details
-
Copy full SHA for 595206b - Browse repository at this point
Copy the full SHA 595206bView commit details -
Configuration menu - View commit details
-
Copy full SHA for a25f867 - Browse repository at this point
Copy the full SHA a25f867View commit details -
Consolidate fresh, fresh_async, fresh_generator
This one comes down to personal opinion I suppose. I don't feel strongly about it.
Configuration menu - View commit details
-
Copy full SHA for ad4b8d9 - Browse repository at this point
Copy the full SHA ad4b8d9View commit details -
Configuration menu - View commit details
-
Copy full SHA for 17a9d52 - Browse repository at this point
Copy the full SHA 17a9d52View commit details
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.