Questions/issues regarding generators

Andreas Rossberg rossberg at google.com
Thu Mar 7 11:05:30 PST 2013


On 7 March 2013 18:30, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:
> On Mar 7, 2013, at 7:37 AM, Andreas Rossberg wrote:
>>
>> 1) Are the methods of a generator object installed as frozen
>> properties? (I hope so, otherwise it would be more difficult to
>> aggressively optimise generators.)
>
> We discussed the factoring of the generator objects at the Nov 27 meeting.
> https://github.com/rwldrn/tc39-notes/tree/master/es6/2012-11 there is a
> sketch of the hierarchy we agreed to in in the notes
> https://dl.dropbox.com/u/3531958/tc39/generator-diagram-1.jpg
>
> TThe design I presented at the meeting is very close to that final one,
> answers this question, and is easier to read:
> http://wiki.ecmascript.org/doku.php?id=meetings:proposed_generator_class_hierarcy_nov_2013.png

Ah, thanks. It seems that I missed that part of the meeting. I'll have a look.


Me:
> - The generator/iterable/iterator separation is somewhat incoherent.
> In particular, it makes no sense that it is a suitable implementation
> of an .iterator method to just return 'this', as it does for
> generators. The implicit contract of the .iterator method should be
> that it returns a _fresh_ iterator, otherwise many abstractions over
> iterables can't reliably work. As a simple example, consider:
>
>  // zip : (iterable, iterable) -> iterable
>  function zip(iterable1, iterable2) {
>    let it1 = iterable1.iterator()
>    let it2 = iterable2.iterator()
>    let result = []
>    try {
>      while (true) result.push([it1.next(), it2.next()])
>    } catch(e) {
>      if (isStopIteration(e)) return result
>      throw e
>    }
>  }
>
> You would expect that for any pair of iterables, zip creates an array
> that pairs the values of both. But is a generator object a proper
> iterable? No. It has an .iterator method alright, but it does not meet
> the aforementioned contract! Consider:
>
>  let rangeAsArray = [1, 2, 3, 4]
>  let dup = zip(rangeAsArray, rangeAsArray)  // [[1,1], [2,2], [3,3], [4,4]]
>
> and contrast with:
>
>  function* enum(from, to) { for (let i = from; i <= to; ++i) yield i }
>
>  let rangeAsGenerator = enum(1, 4)
>  let dup = zip(rangeAsGenerator, rangeAsGenerator)  // Oops!
>
> Although a generator supposedly is an iterable, the second zip will
> fail to produce the desired result, and returns garbage instead.

Allen:
> I'm not sure I convinced by this.  An iterator instance represent a single
> specific iteration.  Your second example is really a user bug and should be
> coded as:
> let dup = zip(enum(1,4), enum(1,4));
>
> Zip's informal contract should state that if iterators are passed as
> arguments they need to be distinct objects. If you want to implement it
> defensively, you can add a  check for that pre-condition.

I have to disagree here. That is just evading the question what the
contract for .iterator is. Either it is supposed to create new state
or it isn't. It's not a very useful contract to say that it can be
both, because then you cannot reliably program against it. And from
the desire to support collections as iterables it pretty much follows
that it should create fresh state (and under that assumption, there is
no bug in zip).


>> The problem boils down to the question whether a generator function
>> should return an iterable or an iterator. The current semantics
>> (inherited from Python) tries to side-step the question by answering:
>> "um, both". But as the example demonstrates, that is not a coherent
>> answer.
>
> I think it is pretty clear that the current semantics is that a generator
> function always returns an iterator.

Well, then it should not have an .iterator method, and for-of should
note invoke that method. But clearly, that would make it harder to use
generators with user-defined iteration abstractions.

But your comment about cloning actually made me think about another
solution: you can make a generator object _truly_ both an iterator
_and_ an iterable by defining its .iterator method to be a clone
method. Importantly, in the normal use case (for-of), that cloning
would not actually be observable, and could easily be avoided by an
implementation.

/Andreas


More information about the es-discuss mailing list