Skip to content
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
wants to merge 9 commits into from
Closed

Commits on Aug 6, 2015

  1. generators

    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.
    samwgoldman committed Aug 6, 2015
    Configuration menu
    Copy the full SHA
    c9ddfd6 View commit details
    Browse the repository at this point in the history

Commits on Aug 21, 2015

  1. Configuration menu
    Copy the full SHA
    673271c View commit details
    Browse the repository at this point in the history

Commits on Sep 4, 2015

  1. Configuration menu
    Copy the full SHA
    661e9d4 View commit details
    Browse the repository at this point in the history
  2. 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.
    samwgoldman committed Sep 4, 2015
    Configuration menu
    Copy the full SHA
    898851a View commit details
    Browse the repository at this point in the history
  3. Configuration menu
    Copy the full SHA
    5706251 View commit details
    Browse the repository at this point in the history

Commits on Sep 5, 2015

  1. 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
    samwgoldman committed Sep 5, 2015
    Configuration menu
    Copy the full SHA
    595206b View commit details
    Browse the repository at this point in the history
  2. Configuration menu
    Copy the full SHA
    a25f867 View commit details
    Browse the repository at this point in the history
  3. Consolidate fresh, fresh_async, fresh_generator

    This one comes down to personal opinion I suppose. I don't feel strongly
    about it.
    samwgoldman committed Sep 5, 2015
    Configuration menu
    Copy the full SHA
    ad4b8d9 View commit details
    Browse the repository at this point in the history
  4. Configuration menu
    Copy the full SHA
    17a9d52 View commit details
    Browse the repository at this point in the history