Questions/issues regarding generators

Dmitry Lomov dslomov at google.com
Fri Mar 15 01:51:25 PDT 2013


In the ideal (from my point of view) world, no object will have both an
iterator() and a next() method together (so Iterator and Iterable would be
different entities; the first having an internal state, and the second
stateless). So your example would be:

var iterable = getSomeIterable();
var i = it.iterator();
var x0 = i.next(), x1 = i.next(), x2 = i.next();
for (let x of iterable) { ... }

and 'for .. of', very clearly from this code, restarts iteration.

I can imagine the world where 'for .. of' iterates over iterators as well
(by calling their 'next()' method directly; so the spec would be "if
iterator() exists, use it, otherwise if next() exists, use it"). In this
world 'for(let x of i)' would continue a started iteration while 'for (let
x of iterable)' would start a new one.

I can also imagine the world where the iterator changes its nature - once
it is created, potentially an iterator and potentially an iterable. Once
you call next() on it, you've lost the ability to call an iterator() on it.
I think that would be the logical implication of cloning semantics. But the
more I think of it the more I feel that this way lies madness - so I guess
you are right and a sane cloning semantic does not exist.

Dmitry



On Thu, Mar 14, 2013 at 10:54 PM, Brendan Eich <brendan at mozilla.com> wrote:

> Consider:
>
>     var i = getSomeIterator();
>     var x0 = i.next(), x1 = i.next(), x2 = i.next();
>     for (let x of i) {
>         ...
>     }
>
> Should the loop iterate over x0, x1, and x2? That's what would (have to)
> happen if i[@iterator]() returned a cloneof the iterator ireset to the
> starting "position".
>
> Of course the iteration protocol we have in Harmony has no notion of
> position, or memory, or any particular thing that might be needed to replay
> x0, x1, and x2.
>
> Cloning i at its "current position" (if such a notion exists) has the
> problem that Andreas objected to in the o.p.
>
> Not cloning i, making iter[@iterator]() === iter as in Python, solves the
> problem minimally.
>
> I don't see a way to specify iterator cloning as part of the iteration
> protocol. What am I missing?
>
> /be
>
> Dmitry Lomov wrote:
>
>>
>> (I'll address comments from both your e-mails here)
>>
>> On Tue, Mar 12, 2013 at 7:56 AM, Jason Orendorff <
>> jason.orendorff at gmail.com <mailto:jason.orendorff at gmail.**com<jason.orendorff at gmail.com>>>
>> wrote:
>>
>>     On Tue, Mar 12, 2013 at 1:06 AM, Dmitry Lomov <dslomov at google.com
>>     <mailto:dslomov at google.com>> wrote:
>>
>>         At a risk of repeating myself, 'open()' scenario is handled
>>         perfectly well with the iterable (see my example). Example
>>         where things truly cannot be reiterated (I am not sure why
>>         network stream is such an example - the network connection can
>>         always be opened twice) are rare. One possibility will be to
>>         throw on the second call to iterator().
>>
>>     Gosh, maybe we are irreconcilable then. Implicitly opening network
>>     connections many times seems bad to me. Same for reopening files,
>>     actually.
>>
>>
>> I do not think we are irreconcilable. Clearly there is a library design
>> choice here. A designer of a particular library for file/network IO may or
>> may not consider opening a file on 'iterator()' call too implicit. I think
>> it is not too implicit, while you appear to think otherwise.
>>
>> In the world with Iterables, the library designer can easily disallow
>> iterating the result of open a second time - as I suggested above, if for
>> whatever reason the sequence cannot be re-iterated, iterator() method can
>> throw on second call. In that case, attempt to zip a file with itself will
>> throw when zip calls the iterator method a second time, and that will be an
>> early failure with a clear cause.
>>
>> However, non-reiterable iterables are a fringe case - maybe 10% of
>> iterators are non-re-iterable even by the standards you suggest (expensive
>> operations on iteration start). [I am being generous here; seems that all
>> allegedly non-reiterable examples suggested so far has been related to I/O;
>> given that I/O libraries are generally asynchronous in ES, I/O is generally
>> not very amenable to be implemented as iterators, since in general results
>> of I/O operations are only available in a callback, and not on-demand, as
>> next() method would require]. My educated guess would be that 90%
>> iterators/iterables in the wild would be easily re-iterable, as they would
>> be results of operations over collections (such as filter, map, zip and
>> similar). This is a baby that gets thrown with the water, not the
>> "non-restartable" iterators
>>
>>
>>     This semantics is sound and consistent, but there is a problem: by
>>     that logic, the first call 'zip(rangeAsArray, rangeAsArray)' also
>>     has all the appearances of a user error! It requires careful
>>     analysis and thinking to convince oneself that it is indeed
>>     correct. Well, maybe not in a simple case when the initializer of
>>     rangeAsArray is an array literal, but as soon as the initializer
>>     is more complicated - say an external function, you can never be sure.
>>
>>
>> > But you could argue the same way for literally any other operation on
>> an object. 'rangeAsArray.length', for example, would also be nonsensical if
>> rangeAsArray turns out to be some other sort of object and not an array
>> after all.
>>
>> We do not talk here about arbitrary operations on a random object; we are
>> talking about operations mandated by the language and their semantics. In
>> fact, length is not a bad example of a precedent in this space: after ES5
>>    for (int i = 0; i < obj.length; i++) console.log(obj[i]);
>> works great for all "array-like" data structures, including arrays,
>> strings and typed arrays. It will be nice to achieve the same for
>> iterator(), for..of and generators.
>>
>> > Note that generators return coroutine objects with methods other than
>> just the iteration-related methods. The coroutine state, to my mind, is
>> inherent to the returned object.
>>
>> In the Iterable design, coroutine state would be inherent to a result of
>> iterator(), i.e. co-routine execution begins once iterator() is called.
>>
>>     If we are to presume that this particular kind of bug will be
>>     common in JS, why isn't it common in Python?
>>     If I'm mistaken about Python and this is actually a common problem
>>     there, then I'd reconsider.
>>
>>
>> I am not a deep specialist in Python, but my understanding is that the
>> problem there is mitigated by the common practice of writing iterators.
>> Python is class-based, so typically one iterates over the class instance,
>> and implementation of __iter__ looks like:
>>
>> class MyToDoList:
>>    ...
>>    def __iter__(self):
>>       for task in self.tasks:
>>          if not task.done:
>>             yield task
>>    ...
>>
>> What happens here is that MyToDoList is actually Iterable in the sense I
>> advocate: every call to MyToDoList.__iter__ returns a fresh iterator. Since
>> python developers typically wrap their iterators in a class,
>> iterable/iterator dichotomy is not acute (but search for "python iterators
>> vs iterables" and even "python iterators considered harmful" to see some
>> examples of confused users)
>>
>> I think that in ES, heavy on functions, people will tend to just use
>> "function*() { ... }" way more often than in Python.
>>
>>
>> Dmitry
>>
>>
>>
>>     -j
>>
>>
>> ______________________________**_________________
>> es-discuss mailing list
>> es-discuss at mozilla.org
>> https://mail.mozilla.org/**listinfo/es-discuss<https://mail.mozilla.org/listinfo/es-discuss>
>>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20130315/dfff00f3/attachment-0001.html>


More information about the es-discuss mailing list