-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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.
- Loading branch information
1 parent
44984e0
commit c9ddfd6
Showing
10 changed files
with
297 additions
and
59 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.