yield* desugaring

Allen Wirfs-Brock allen at wirfs-brock.com
Mon May 13 11:57:43 PDT 2013


On May 13, 2013, at 11:08 AM, Andreas Rossberg wrote:

> On 13 May 2013 19:24, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:
>> On May 13, 2013, at 2:07 AM, Andreas Rossberg wrote:
>> [...]
>> Yes, it's a very good idea.  The easy way for an imperative programmer (there are a few of us in the world) to understand yield* is as a yielding loop over an iterator.  Very straight forward to understand rather than describing it as a "mechanism for composing generators" (from the wiki) which I had no idea what it meant until I carefully studied the desugaring.  At that point it because clear that it was just a yielding loop over an iterator  that for some reason was arbitrarily being restricted to being a generator.
>> 
>> That restriction is what is breaking the iterator abstraction.
> 
> See my previous reply. I think there is some confusion here about the
> direction of the abstraction.

Likewise, but I won't argue it again as I think we are converging upon a fine solution.

> 
>> Could you clarify the special-case handling you have in mind? There is nothing in the wiki proposal desugaring of yield* that guarantees  that the delegated object is an actual generator.  All it requires is a "send" method (and, in that desugaring,  "close" plus "throw" if "throw" is actually invoked).
> 
> Exactly. But iterators don't currently have 'send' and 'throw'
> methods. So you would want to do something different for
> generator-like objects then you'd do for other iterators.
> 
Or align the interfaces so that the differences don't exist for the situations when the two abstractions  are reasonable alternatives.

> 
>> Regarding recursive "sends" to an outer generator.  This shouldn't work, according to the wiki proposal.  When executing a yield* the outer generator must be in the "executing" state. Invoking an inner generator from an yield* via a "send" invocation still leaves the outer generator in the "executing" state.  If the inner generator invokes "send" on the outer generator the "send" will throw because the outer is already in the "executing" state.
> 
> The case I was talking about is simply this:
> 
>  function* g() {
>    yield* [1, 2]
>  }
> 
>  var o = g()
>  o.send(undefined)
>  o.send(5)  // what does this mean?
> 
> But I suppose the answer is that the sent value is just dropped on the
> floor, as per the iterator expression interpretation you gave in the
> other post. Makes sense, I guess.

Right.  whenever you "send" a value back to a generator you are doing something that requires specific and specialized understanding of the "receiver".  If you are sending to an arbitrary generator/iterator you should have no expectation other than the the value is likely to be dropped on the floor,. 

>> First why do we need "send" at all.  Why not simply allow an argument to be passed to "next"  (of course, it is already allowed) and leave it up to the generator implementation as to whether or not they pay any attention to it.   Clearly a client needs to be aware when they are using a generator that expects to receive a value back from yield so that fact must be documented in the public contract of that generator.  Once that is done, the client can use "next" as easily as they could use "send".  Of course, if people really like the name "send" we could also provide that method for generators with the meaning:
>>    send(value) {return this.next(value)}
> 
> I happen to dislike the name 'send' a lot and would rather call it
> 'resume'. ;) But your suggestion of merging it with 'next' sounds
> plausible as well.

+1 (myself, really :-)

> 
>> That leaves only "throw" as an issue.  Personally, I'd just make it part of the Iterator interface and provide an Iterator abstract class that provides
>>   throw(exception) {throw exception}
>> as the default "throw" implementation so most iterator authors don't even have to think about it. Short of that, I think having an explicit behavior check for "throw" in the yield* algorithm is a very small cost (that only arises if someone  actually invokes the "throw" method on the outer generator) and would take care of most common situation where "throw" is likely to be invoked on an iterator..
> 
> That might actually work. If we manage to truly unify iterator and
> generator types then you got me convinced.

Which?  The abstract class or the guarded throw in yield* (or both).

I think if we can unify all of this we will have simplified the language in a way that will benefit many users for a long time to come.

Allen

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20130513/ac2b6412/attachment.html>


More information about the es-discuss mailing list