yield* desugaring

Allen Wirfs-Brock allen at wirfs-brock.com
Sun May 12 12:29:26 PDT 2013

I literally just finished making the changes to the ES6 specification draft to fully incorporate generator semantics. I'll will be making that draft available within the next day or two after worked down the bug backlog a bit.

Because this thread started while I was in the middle of that work, I choose to ignore it until I had finished with it (with the exception, that I differed implementing <generator>.close() because I did notice that its utility was being questioned). I've now digested the thread and I want to provide some feedback on it that reflects my spec. work.

First, as a general comment, I don't use direct desugaring within the spec. but instead use the spec. pseudo code formalisms.  This gives me direct access to mechanisms such as internal Completion values and allows me to express behaviors that are difficult or impossible to express via desugarings.

As now specified:

1) I specified yield* such that it will work with any iterator, not just generators. To me, this seems essential.  Otherwise client code is sensitive to other peoples implementation decision (that are subject to change) regarding whether to use a  generator or an object based iterator.  This was easy to accomplish and only requires a one-time behavioral check to determine whether "next" or "send" should be used to retrieve values from the delegated iterator and an behavior guard on invoking "throw" on the delegated iterator.  

2) yield* invokes the @@iterator method on its expression to obtain the iterator. This means you can say things like:
    yield * [2,4,6,8,10];  //individually yield positive integers <= 10.

3) yield* yields the nextResult object produced by the inner iterator. No unwrapping/rewrapping required. 

4) I haven't (yet) provided a "close" method for generators.  I still think we should.  Unwinding via return is clearly the appropriate semantics for "close". Contrary to some concerns expressed on this thread, I don't think there are any issues with distinguishing "close" triggered returns from actual user level concerns. Such returns (all returns) have to pass as Completion values  through the outer body level of the generator and it is easy enough (at the spec level) for me to tag such returns (using a unique, unobservable internal return value) such that there is no confusion with an actual user level return.

I think "close" is an useful and important operation for maintaining finally semantics in two use cases:
      a)       for (v of iterable) {...break;...} /* or return or throw or outer continue*/
The for-of should should automatically invoke "close" if the loop is terminated before exhausting the iterator.  The "close" needs to be guarded with a behavioral check just like I did for yield*.  I think this is a common case and we really should be doing our best effort to maintain the reliability of finally blocks for this common case.
      b)      Any time user code is manually draining a known generator that it opened and decides that it is now done with the generator. They really should close it.  Of course, they may not, but regardless they should be provided with a means to do so and it should be encouraged as a best practice.

The second use case is perhaps a little iffy, but the first really seems fundamental to finally semantics and something that the language can automatically do. To me, it would seem a bit negligent to not handle that situation. 


On May 2, 2013, at 12:24 PM, Andy Wingo wrote:

> Greets,
> Thanks for the kind words!  And thanks also for the new iteration
> proposal; it looks great to my ignorant eyes.  I'll probably take a look
> at for-of next.
> On Thu 02 May 2013 20:07, David Herman <dherman at mozilla.com> writes:
>>> IMHO yield* should be specified to return the result object as-is,
>>> without re-boxing.  This precludes a straightforward desugaring, but it
>>> is probably more flexible.
>> This is an interesting question. I agree that the intuition of yield* is
>> chaining together continuation frames and therefore "just pass on
>> through" seems like the Right Thing. It's only maybe slightly
>> disconcerting that yield* therefore actually gives you additional power
>> that you can't otherwise express locally (i.e., you have to transform
>> the entire containing generator function). But I'm with you, this seems
>> right to me.
> I was working on this today and thought better of my original proposal.
> It would be nice to assume that calling next() on a generator returns a
> { value, done } object, and that is not necessarily the case if we pass
> on the result from calling some other iterator:
>>     let next = send ? g.send(received) : g.throw(received);
> Here "g" might not be a generator object -- though we could specify that
> it is [*] -- and "next" might not have the { value, done } form.  It might
> not even be an object, in which case getting the "value" would fail at
> an even greater distance from the original error.
> This is a very small point in the scheme of things, but it seemed to me
> that desugaring as "yield next.value" was probably less confusing to
> users.  WDYT?
> Regards,
> Andy
> [*] If we specify that "g" is a generator object, then this question is
> moot: the identity of the result of "g.send(receiver)" is not visible.
> _______________________________________________
> es-discuss mailing list
> es-discuss at mozilla.org
> https://mail.mozilla.org/listinfo/es-discuss

More information about the es-discuss mailing list