Why can’t for-of be applied to iterators?
On Jun 8, 2013, at 10:34 PM, Axel Rauschmayer wrote:
I can’t find the rationale as to why that isn’t allowed. Is it because iterators can’t be recognized as safely as iterables?
Both Iterators and Iterables may be ordinary objects. There has to be some way for for-of to recognize one or the other. The way that an Iterable is recognized is by the presence of an @@iterator method. An Iterator is only required to have a "next" method. Checking for the unique symbol @@iterator seems much less likely to produce false positives then checking for "next".
The other way to look at it is that for-of is designed to iterate over the elements of a collection (an Iterable) so that is what it checks for. An Iterator is jus part of the mechanism for implementing for-of iteration.
If we did enable for-of (and Array.from) for iterators, we would get two advantages:
- Generators would not need to have a method @@iterator.
why is this a big deal? It is provided by the standard generator prototype that all generators inherit from.
- Many functions actually return iterators, but have to turn them into iterables (by adding a method @@iterator that returns
this
) to work with for-of.
As a rule of thumb, if an Iterator is produced by a @@iterator method it probably doesn't need to also be an Iterable. If an Iterator is produced by any other method it probably should be an Iterable.
It seems easy enough to define a class that has a @@iterator() {return this} method and then to subclass class it for you specific Iterables.
I can’t find the rationale as to why that isn’t allowed. Is it because iterators can’t be recognized as safely as iterables?
Both Iterators and Iterables may be ordinary objects. There has to be some way for for-of to recognize one or the other. The way that an Iterable is recognized is by the presence of an @@iterator method. An Iterator is only required to have a "next" method. Checking for the unique symbol @@iterator seems much less likely to produce false positives then checking for "next".
True, @@iterator is a much better “type tag”. If @@iterator is checked first, would false positives really be a problem?
If we did enable for-of (and Array.from) for iterators, we would get two advantages:
- Generators would not need to have a method @@iterator.
why is this a big deal? It is provided by the standard generator prototype that all generators inherit from.
It’s not that big of a deal, I just prefer the increased conceptual clarity (which may or may not be worth the additional complexity).
- Many functions actually return iterators, but have to turn them into iterables (by adding a method @@iterator that returns
this
) to work with for-of.As a rule of thumb, if an Iterator is produced by a @@iterator method it probably doesn't need to also be an Iterable. If an Iterator is produced by any other method it probably should be an Iterable.
It seems easy enough to define a class that has a @@iterator() {return this} method and then to subclass class it for you specific Iterables.
True.
Another use case for iterator support: retrieve the first element from an iterator, then iterate over the remaining elements via for-of.
It seems easy enough to define a class that has a @@iterator() {return this} method and then to subclass class it for you specific Iterables.
One more thing that might be problematic with the current approach: If you create combinators for iterators (similar to Python’s itertools [1]) then the returned iterators also need to implement this pattern. Otherwise, for-of couldn’t iterate over them. Example:
for(let elem of filter(keys(myObj), x => x.length > 0)) {
}
Thus, the pattern becomes the norm instead of the exception. Then again, maybe that’s not a big deal, either.
On Sun, Jun 9, 2013 at 2:17 AM, Axel Rauschmayer <axel at rauschma.de> wrote:
Another use case for iterator support: retrieve the first element from an
iterator, then iterate over the remaining elements via for-of.
Right, this happens whenever you're doing a fold and your combining function has no handy "zero" value. For the sake of concreteness let's say we're writing code for a robot kitchen, and we've got a function for taking a collection of bowls and dumping all the contents into the first bowl. In Python:
def combine(bowls):
iterator = iter(bowls)
first_bowl = next(iterator, None)
for bowl in iterator:
dump_into(bowl, first_bowl)
In JS the same function would be:
from "yet_unspecified_standard_module" import iteratorSymbol;
function combine(bowls) {
let iterator = bowls[iteratorSymbol]();
let {value: firstBowl, done} = iterator.next();
if (done)
return; // no bowls at all
let rest = {};
Object.defineProperty(rest, iteratorSymbol, {value: () =>
iterator}); for (let bowl of rest) dumpInto(bowl, firstBowl); }
If you don't mind copying the source collection into an array, you can use destructuring instead:
let [?first, ...rest] = bowls;
At least, I think that's the current proposed destructuring syntax/semantics. Of course this only works if the use case has a structure that the destructuring mini-language can express.
Other use cases come up too: (1) you want to process a sequence in one loop until you reach some marker, and then break out of the first loop and process the rest in another loop; or (2) you want to pass the iterator to a separate function that consumes a subsequence of the input. (For example, when the input sequence is from a file, and you're doing some light parsing.)
I think it's a mistake for iterators not to be iterable. We will have error
messages like "Iterator object is not iterable", and users will say
"really, JavaScript?". Requiring users to build pseudo-collection objects
like rest
in these use cases seems awfully bureaucratic, and hard to
justify without resorting to types.
On Jun 9, 2013, at 2:34 AM, Jason Orendorff wrote:
On Sun, Jun 9, 2013 at 2:17 AM, Axel Rauschmayer <axel at rauschma.de> wrote:
Another use case for iterator support: retrieve the first element from an iterator, then iterate over the remaining elements via for-of.
Right, this happens whenever you're doing a fold and your combining function has no handy "zero" value. For the sake of concreteness let's say we're writing code for a robot kitchen, and we've got a function for taking a collection of bowls and dumping all the contents into the first bowl. In Python:
def combine(bowls): iterator = iter(bowls) first_bowl = next(iterator, None) for bowl in iterator: dump_into(bowl, first_bowl)
In JS the same function would be:
from "yet_unspecified_standard_module" import iteratorSymbol; function combine(bowls) { let iterator = bowls[iteratorSymbol](); let {value: firstBowl, done} = iterator.next(); if (done) return; // no bowls at all let rest = {}; Object.defineProperty(rest, iteratorSymbol, {value: () => iterator}); for (let bowl of rest) dumpInto(bowl, firstBowl); }
If you don't mind copying the source collection into an array, you can use destructuring instead:
let [?first, ...rest] = bowls;
At least, I think that's the current proposed destructuring syntax/semantics. Of course this only works if the use case has a structure that the destructuring mini-language can express.
Other use cases come up too: (1) you want to process a sequence in one loop until you reach some marker, and then break out of the first loop and process the rest in another loop; or (2) you want to pass the iterator to a separate function that consumes a subsequence of the input. (For example, when the input sequence is from a file, and you're doing some light parsing.)
I think it's a mistake for iterators not to be iterable. We will have error messages like "Iterator object is not iterable", and users will say "really, JavaScript?". Requiring users to build pseudo-collection objects like
rest
in these use cases seems awfully bureaucratic, and hard to justify without resorting to types.
Iterators can be Iterables and any Iterator created as a generator will automatically be an Iterator.
But it really comes down to the startup behavior of for-of if the value of the |of| expression is not an Iterable.
Currently the spec. days that if it is not an Iterable the for-of loop doesn't run (although some of the details in the For In/Of Expression Evaluation looks a little bogus). It would be easy enough to to spec. it such that if the value is not an Iterable (does not have an @@iterator property) then it just assumes that it must already be an Iterator.
in that case we could say:
function combine(bowls) {
let iterator = bowls[iteratorSymbol]();
let {value: firstBowl, done} = iterator.next();
if (done)
return; // no bowls at all
for (let bowl of iterator)
dumpInto(bowl, firstBowl);
}
Allen Wirfs-Brock wrote:
Currently the spec. days that if it is not an Iterable the for-of loop doesn't run (although some of the details in the For In/Of Expression Evaluation looks a little bogus). It would be easy enough to to spec. it such that if the value is not an Iterable (does not have an @@iterator property) then it just assumes that it must already be an Iterator.
Of we could do what has been proposed since ES4 days, and (after Python) make @@iterator for an iterator return that self-same iterator:
js> a = [1,2,3] [1, 2, 3] js> a.iterator
function iterator() { [native code] } js> it = a.iterator() ({}) js> it.iterator
function iterator() { [native code] } js> it.iterator() === it
true
(SpiderMonkey latest, no unique iteratorSymbol yet.)
Just assuming an iterator if there is no @@iterator means trying .next() with exception suppression when there is no 'next' or its value is not a function, right? That seems worse than the Python way. We don't want the case where there is an @@iterator to suppress missing/non-callable 'next'.
in that case we could say:
function combine(bowls) { let iterator = bowls[iteratorSymbol](); let {value: firstBowl, done} = iterator.next(); if (done) return; // no bowls at all for (let bowl of iterator) dumpInto(bowl, firstBowl); }
This is as good as it gets, btw -- Jason's Python didn't handle the "no bowls at all" case.
Do you want a bugs.ecmascript.org ticket on this?
On Jun 9, 2013, at 7:17 PM, Brendan Eich wrote:
Allen Wirfs-Brock wrote:
Currently the spec. days that if it is not an Iterable the for-of loop doesn't run (although some of the details in the For In/Of Expression Evaluation looks a little bogus). It would be easy enough to to spec. it such that if the value is not an Iterable (does not have an @@iterator property) then it just assumes that it must already be an Iterator.
Of we could do what has been proposed since ES4 days, and (after Python) make @@iterator for an iterator return that self-same iterator:
That's what is essentially currently spec'ed. All the built-in iterators are specified in that way and for-or essentially expects that behavior. By reading of this thread is that people were looking for ways to avoid having to explicitly define a method like [iteratorSymbol] () {return this} for those situations.
js> a = [1,2,3] [1, 2, 3] js> a.iterator function iterator() { [native code] } js> it = a.iterator() ({}) js> it.iterator function iterator() { [native code] } js> it.iterator() === it true
(SpiderMonkey latest, no unique iteratorSymbol yet.)
Just assuming an iterator if there is no @@iterator means trying .next() with exception suppression when there is no 'next' or its value is not a function, right?
I won't want to suppress any exceptions. If next wasn't there you would get an exeption when for-of tried to invoke it.
That seems worse than the Python way. We don't want the case where there is an @@iterator to suppress missing/non-callable 'next'.
No supression, the semantics of e in for (let x of e) is would essentially be let iter = Reflect.has(e,@@iterator)? e@@iterator : e;
in that case we could say:
function combine(bowls) { let iterator = bowlsiteratorSymbol; let {value: firstBowl, done} = iterator.next(); if (done) return; // no bowls at all for (let bowl of iterator) dumpInto(bowl, firstBowl); }
This is as good as it gets, btw -- Jason's Python didn't handle the "no bowls at all" case.
Do you want a bugs.ecmascript.org ticket on this?
I really just need us to reach consensus on what we want to have.
I think what is currently spec'ed (for-of requires an @@iterator) is fine. But I also think falling back to just using the supplied value as the iterator would be ok.
On Sun 09 Jun 2013 11:34, Jason Orendorff <jason.orendorff at gmail.com> writes:
I think it's a mistake for iterators not to be iterable.
I agree, FWIW.
I think I would go farther and suggest that only iterators be iterable. That way, the RHS of a for-of is expected to be an iterator. In the worst case you end up having:
var a = [1, 2, 3, 4] for (let x of values(a)) ...
instead of
for (let x of a) ...
which to my eye is better anyway. Making an "itertools"-like library difficult is a definite drawback of the current spec.
Andy
We really need to be able to do for-of with arrays and node lists etc, without having to call .values() on it. It is such a common operation that we should not tax this further.
Erik Arvidsson wrote:
We really need to be able to do for-of with arrays and node lists etc, without having to call .values() on it. It is such a common operation that we should not tax this further.
Agreed. Consider Peter Norvig's Sudoku solver, which I ported to JS1.7 years ago, ported to ES6:
gist.github.com/anonymous/5753636
This code is nearly as concise as the Python original. Requiring values() calls all over does no one any favors.
The idea that iterators return themselves from their @@iterator (iter) hook has years of actual programmer mileage in Python. I take that experience seriously, compared to arguments from purity that tax the common use-cases.
Brendan Eich wrote:
Erik Arvidsson wrote:
We really need to be able to do for-of with arrays and node lists etc, without having to call .values() on it. It is such a common operation that we should not tax this further.
Agreed. Consider Peter Norvig's Sudoku solver, which I ported to JS1.7 years ago, ported to ES6:
Better version at
gist.github.com/BrendanEich/5753666
(Stripped iterator left-overs from JS1.7 era -- hooray for iterable Arrays in ES6!)
The idea that iterators return themselves from their @@iterator (iter) hook has years of actual programmer mileage in Python. I take that experience seriously, compared to arguments from purity that tax the common use-cases.
I’d be happy with either solution, but let’s compare:
AFAICT, the tax is on for-of (and Array.from()): they need to additionally check whether an object is an iterator.
On the other hand, turning every iterator into an iterable puts the burden on people implementing iterators: they have to implement the iterable interface. And it becomes harder to distinguish the interfaces, should you need to [1]. IMO, the purity argument also counts: this technique implies that iterators are a subset of iterables, but that isn’t true: For iterables, each invocation of iterator
creates a new object, for iterators, it doesn’t.
As an aside, Python 3 uses a special name for the next
method (it previously didn’t). Maybe ES6 should use a symbol here, too. That would eliminate the concerns about false positives, should for-of ever work with iterators.
On Tue 11 Jun 2013 11:08, Axel Rauschmayer <axel at rauschma.de> writes:
The idea that iterators return themselves from their @@iterator (__iter__) hook has years of actual programmer mileage in Python. I take that experience seriously, compared to arguments from purity that tax the common use-cases.
I’d be happy with either solution, but let’s compare:
AFAICT, the tax is on for-of (and Array.from()): they need to additionally check whether an object is an iterator.
So, my proposal would be to make the for-of RHS expect an iterator, and remove the concept of "iterable". So there's no need to check for anything; you just assume you have an iterator.
I don't find the cost of this change (let's not say "tax" ;) terrible:
for (x of y.values()) f(x);
Or
for (x of values(y)) f(x);
Though the former seems more javascripty, my impression was that we were not adding more prototype methods, in favor of using modules instead. I don't have a @horse_js in that race though.
The reason I think this is OK is because:
for (x in y) f(x)
translates as
for (x of keys(y)) f(x)
so to me it is natural that values should also come from a function/method call:
for (x of values(y)) f(x)
As iterators and for-of become more prevelant, I think there are actually few times that you want the default iterator -- the reason being that you will deal more with iterators and less with reified containers like arrays. The evolution of Python seems to show this, and (dare I mention it?) Dart also seems to be going in this direction.
So that's the "cost" side. The benefit of expecting the RHS to be an iterator is that itertools-like combinators become easier to write, and that can only be a good thing, as we avoid making intermediate garbage containers and eager computation. As a side benefit, we could remove the concept of "iterable" from the spec. Also we remove the opaque @@iterator call from for-of, which can have better error-reporting characteristics.
Again, MHO :)
On 11 June 2013 11:34, Andy Wingo <wingo at igalia.com> wrote:
On Tue 11 Jun 2013 11:08, Axel Rauschmayer <axel at rauschma.de> writes:
The idea that iterators return themselves from their @@iterator (__iter__) hook has years of actual programmer mileage in Python. I take that experience seriously, compared to arguments from purity that tax the common use-cases.
I’d be happy with either solution, but let’s compare:
AFAICT, the tax is on for-of (and Array.from()): they need to additionally check whether an object is an iterator.
So, my proposal would be to make the for-of RHS expect an iterator, and remove the concept of "iterable". So there's no need to check for anything; you just assume you have an iterator.
I don't find the cost of this change (let's not say "tax" ;) terrible:
for (x of y.values()) f(x);
Or
for (x of values(y)) f(x);
Though the former seems more javascripty, my impression was that we were not adding more prototype methods, in favor of using modules instead. I don't have a @horse_js in that race though.
The reason I think this is OK is because:
for (x in y) f(x)
translates as
for (x of keys(y)) f(x)
so to me it is natural that values should also come from a function/method call:
for (x of values(y)) f(x)
As iterators and for-of become more prevelant, I think there are actually few times that you want the default iterator -- the reason being that you will deal more with iterators and less with reified containers like arrays. The evolution of Python seems to show this, and (dare I mention it?) Dart also seems to be going in this direction.
So that's the "cost" side. The benefit of expecting the RHS to be an iterator is that itertools-like combinators become easier to write, and that can only be a good thing, as we avoid making intermediate garbage containers and eager computation. As a side benefit, we could remove the concept of "iterable" from the spec. Also we remove the opaque @@iterator call from for-of, which can have better error-reporting characteristics.
I think Andy is right. The story with iterables is rather messy, and their benefits (default iterators) might not outweigh their cost (and in fact, introduce their own usability costs downstream). I like the suggestion of just dropping them altogether.
On Tue, Jun 11, 2013 at 2:08 AM, Axel Rauschmayer <axel at rauschma.de> wrote:
On the other hand, turning every iterator into an iterable puts the burden on people implementing iterators: they have to implement the iterable interface.
That's... not really a burden. It's literally just adding an @@iterator method whose body is "return this;". It's the right place to put the burden, too - manual iterator authors are much, much fewer than for-of users.
And it becomes harder to distinguish the interfaces, should you need to [1]. IMO, the purity argument also counts: this technique implies that iterators are a subset of iterables, but that isn’t true: For iterables, each invocation of
iterator
creates a new object, for iterators, it doesn’t.
As noted in the first comment, it's trivial to check for the difference in all well-behaved cases by just seeing if "obj === objiterator" is true or false.
I found this comparison with C# illuminating, trying to make sense of this thread.
- It uses duck-typing for its
foreach
loops, allowing them to work with anything that has aGetEnumerator
method. - All of its collections do have this, by virtue of inheriting from
IEnumerable
orIEnumerable<T>
. - An "enumerator" has
MoveNext
,Current
, andReset
. - Generator functions return enumerables, not enumerators. Calling
GetEnumerator
on the returned enumerable gives a fresh instance, so a single call to a generator function can, by virtue of spawning multiple enumerators, allow you to create multiple invocations stepping through the generator function.
I believe this is very close to how ES works, as specced:
for
-of
looks for the@@iterator
symbol, i.e. it works on iterables which have a method for getting new iterator instances.- Arrays and other collections all are iterable, e.g. arrays have an
@@iterator
that returns a new iterator for their values (thevalues()
method does the same). - Iterators are our friends with the
next
and optionalthrow
methods.
Generator functions are a bit different in ES however:
- Each call to a generator function returns a single iterator that is also an iterable. Getting
@@iterator
returns itself. So a single call to a generator function always creates a single invocation of the generator function to step through; you cannot generate multiple invocations.
I guess this is kind of the opposite direction Andy and Andreas have been going, but it does seem conceptually clearer to me to separate the concept of "something that can be iterated" (iterable) from "an instance of that iteration" (iterator). The former doesn't have state, usually, whereas the latter does. Hrm.
On 11 June 2013 14:50, Tab Atkins Jr. <jackalmage at gmail.com> wrote:
On Tue, Jun 11, 2013 at 2:08 AM, Axel Rauschmayer <axel at rauschma.de> wrote:
And it becomes harder to distinguish the interfaces, should you need to [1]. IMO, the purity argument also counts: this technique implies that iterators are a subset of iterables, but that isn’t true: For iterables, each invocation of
iterator
creates a new object, for iterators, it doesn’t.As noted in the first comment, it's trivial to check for the difference in all well-behaved cases by just seeing if "obj === objiterator" is true or false.
That makes a terrible API, though. I think Axel has a valid point that the distinction between iterators and iterables is fuzzy enough to be more confusing and error-prone than useful.
On 11 June 2013 15:16, Domenic Denicola <domenic at domenicdenicola.com> wrote:
I guess this is kind of the opposite direction Andy and Andreas have been going, but it does seem conceptually clearer to me to separate the concept of "something that can be iterated" (iterable) from "an instance of that iteration" (iterator). The former doesn't have state, usually, whereas the latter does. Hrm.
FWIW, I had argued for a cleaner separation along the lines you suggest a while ago (see esdiscuss/2013-March/029004). However, I think not needing the notion of iterable at all would be even better.
On Tue, Jun 11, 2013 at 4:34 AM, Andy Wingo <wingo at igalia.com> wrote:
So, my proposal would be to make the for-of RHS expect an iterator, and remove the concept of "iterable". So there's no need to check for anything; you just assume you have an iterator.
I don't find the cost of this change (let's not say "tax" ;) terrible:
for (x of y.values()) f(x);
I can see the tradeoffs between a tax on the performance of loops and a tax on people who want to implement iterators without using generators (hint: use a generator). But a tax on everyone who wants to write a loop seems clearly much worse.
As iterators and for-of become more prevelant, I think there are
actually few times that you want the default iterator -- the reason being that you will deal more with iterators and less with reified containers like arrays. The evolution of Python seems to show this, and (dare I mention it?) Dart also seems to be going in this direction.
I can't speak for Dart, but the evolution of Python shows no such thing.
Please look at any Python codebase. The right-hand side of a for loop is most often just an identifier or simple expression naming a collection. You will see a few calls to enumerate(), .items(), and so on, but they do not predominate. Sometimes it's a list literal.
I will quantify this later today.
On Tue, Jun 11, 2013 at 8:42 AM, Jason Orendorff <jason.orendorff at gmail.com>wrote:
Please look at any Python codebase. The right-hand side of a for loop is most often just an identifier or simple expression naming a collection. You will see a few calls to enumerate(), .items(), and so on, but they do not predominate. Sometimes it's a list literal.
I will quantify this later today.
In case I don't get to this, here is the code you can use: gist.github.com/jorendorff/5757221
It's easy to run on any Python codebase. In the Python 3.3.1 distribution, for example, only about 12% of for loops require an explicit indication of what kind of iterator is wanted. That is, the default is used about 88% of the time.
49.0% - N - just an identifier
15.0% - R - call to range()
15.5% - A - an attribute-expression, like self.parts
6.4% - I - call to .items() or .iteritems()
4.2% - L - literal string, tuple, list, or set
2.7% - E - call to enumerate()
1.3% - K - call to .keys() or .iterkeys()
1.0% - V - call to .values() or .itervalues()
4.9% - O - everything else
(examples: module.split('.')[1:], dirs[:], args[1:],
fixers[node.type])
On 6/11/2013 5:50 AM, Tab Atkins Jr. wrote:
On Tue, Jun 11, 2013 at 2:08 AM, Axel Rauschmayer <axel at rauschma.de> wrote:
On the other hand, turning every iterator into an iterable puts the burden on people implementing iterators: they have to implement the iterable interface. That's... not really a burden. It's literally just adding an @@iterator method whose body is "return this;". It's the right place to put the burden, too - manual iterator authors are much, much fewer than for-of users.
It may be worth noting that, while it hasn't made it into the ES6 spec
(yet?), the iterators strawman [1] (and modules_standard strawman [2])
had an Iterator
class that all the builtin iterators (Map, Set,
Generator, etc.) inherited from who's prototype had @@iterator built in.
So usually the only burden for iterator authors was to create a class
that inherited from Iterator.
import { Iterator } from '@iter';
class MyIterator extends Iterator { /*...*/ }
I believe Iterator should be an interface and not a class so I could extend Array or ArrayLike implementing Iterator, when/if necessary, in order to have a for/of compatible class.
We don't have interfaces ... I know, we could have mixins though, compatible with @@things too.
My 2 cents
On Jun 11, 2013, at 9:41 AM, Brandon Benvie wrote:
On 6/11/2013 5:50 AM, Tab Atkins Jr. wrote:
On Tue, Jun 11, 2013 at 2:08 AM, Axel Rauschmayer <axel at rauschma.de> wrote:
On the other hand, turning every iterator into an iterable puts the burden on people implementing iterators: they have to implement the iterable interface. That's... not really a burden. It's literally just adding an @@iterator method whose body is "return this;". It's the right place to put the burden, too - manual iterator authors are much, much fewer than for-of users.
It may be worth noting that, while it hasn't made it into the ES6 spec (yet?), the iterators strawman [1] (and modules_standard strawman [2]) had an
Iterator
class that all the builtin iterators (Map, Set, Generator, etc.) inherited from who's prototype had @@iterator built in. So usually the only burden for iterator authors was to create a class that inherited from Iterator.import { Iterator } from '@iter'; class MyIterator extends Iterator { /.../ }
t would be trivial to specify an Iterator abstract class of this sort and I personally would be fine having it. However, I don't think we have consensus yet that it is desirable to include such abstract classes in the ES standard library. Currently, in the spec draft, the built-in collection Iterators all individually implement @@iterator() {return this}
Note that such an abstract class is even a reasonable substitute for use of an object literal to define an iterator:
import {Iterator} from "standard iteration module"; function giveMeAEmptyIterator( ) { return new class extends Iterator { next() {return {done: true}} } }
rather than:
import {iteratorSymbol} from "standard iteration module"; function giveMeAEmptyIterator( ) { return { next() {return {done: true}}, [iteratorSymbol] () {return this} } }
But, really, for-of (and Arrray.from and friends) should just accept either an Iterable or an Iterator with priority given to Iterable
Then the above an most conveniently be coded as:
function giveMeAEmptyIterator( ) { return { next() {return {done: true}}, } }
The for-of behavior should be: If ducktype check of the "of" value) is true use result of invoking @@iterator on the "of" value as the Iterator else use "of" value as the Iterator
This is a very cheap test and only occurs once on entry to a loop
On Jun 11, 2013, at 10:22 AM, Andrea Giammarchi wrote:
I believe Iterator should be an interface and not a class so I could extend Array or ArrayLike implementing Iterator, when/if necessary, in order to have a for/of compatible class.
We don't have interfaces ... I know, we could have mixins though, compatible with @@things too.
Even if we had an Iterator abstract class, it would simply be there as an connivence for Iterator implementors. I would never specify for-of as doing a instanceof-like check on Iterator. Duck-typing is what is need in this situation.
While we don't have interfaces as a linguistic form, we use implicit interfaces all the time. Any duck-typing check, whether explicit or implicit (via an unguarded method call) is conceptually checking for an implicit interface.
so you are saying instanceof would be bad while 'nextable' (same 'thenable' concept) is a better approach?
Ages ago, I was using PHP SPL a lot and found the interface approach very simple and nice, it's more suitable for Proxy objects in JS though... that's why I've talked about mixin, to drop repeasted code that will implement same thing for every ArrayLike object: www.php.net/manual/en/class.iterator.php (Empty Iterator)
I also think is hard to distinguish between for/in'able' and for/of'able' since first kind always worked with pairs/entries like objects while second seems to be more ... well ... complicated for no concrete reason (from a user prospective)
In any case, if a generic next(){} function to attach to any instance would make it iterable then is fine and easy enough to explain.
On Jun 11, 2013, at 11:07 AM, Andrea Giammarchi wrote:
so you are saying instanceof would be bad while 'nextable' (same 'thenable' concept) is a better approach?
of course
Ages ago, I was using PHP SPL a lot and found the interface approach very simple and nice, it's more suitable for Proxy objects in JS though... that's why I've talked about mixin, to drop repeasted code that will implement same thing for every ArrayLike object: www.php.net/manual/en/class.iterator.php (Empty Iterator)
I also think is hard to distinguish between for/in'able' and for/of'able' since first kind always worked with pairs/entries like objects while second seems to be more ... well ... complicated for no concrete reason (from a user prospective)
All objects are for/in-able since for/in uses a MOP level operation that all object must support. There is nothing you can do to change its behavior short of using a Proxy. There isn't really very much conceptual complexity to Iterable/Iterator. The most important concept is that for any specific object abstraction there can be many meaningful ways to "iterate" over the information it provides. Iterator access method (keys, values, items, anything you want to define) provide a what to select a particular form of iteration. An iterator objet simply represents a specific active iteration over a particular object.
Perhaps the similarity of the words "Iterator" and "Iterable" causes some confusion. I think it's fine if you want to think of "Iterable" as being equivalent to saying "nextable".
In any case, if a generic next(){} function to attach to any instance would make it iterable then is fine and easy enough to explain.
You don't attach a generic next method, you simply define an appropriate one.
Axel Rauschmayer wrote:
The idea that iterators return themselves from their @@iterator (iter) hook has years of actual programmer mileage in Python. I take that experience seriously, compared to arguments from purity that tax the common use-cases.
I’d be happy with either solution, but let’s compare:
AFAICT, the tax is on for-of (and Array.from()): they need to additionally check whether an object is an iterator.
On the other hand, turning every iterator into an iterable puts the burden on people implementing iterators: they have to implement the iterable interface. And it becomes harder to distinguish the interfaces, should you need to [1].
This is two-edged: if you want an iterator to be iterable other than by returning itself, you can do that with the Pythonic protocol on which we based the ES4/6 design.
IMO, the purity argument also counts: this technique implies that iterators are a subset of iterables, but that isn’t true: For iterables, each invocation of
iterator
creates a new object, for iterators, it doesn’t.
That's a matter of definition or choice-of-axiom, though.
As an aside, Python 3 uses a special name for the
next
method (it previously didn’t). Maybe ES6 should use a symbol here, too. That would eliminate the concerns about false positives, should for-of ever work with iterators.
There's no need for two symbols, @iterator and @next, given the first. The Pythonic protocol we based the design on always calls @iterator, avoiding false positives.
Andreas Rossberg wrote:
On 11 June 2013 14:50, Tab Atkins Jr.<jackalmage at gmail.com> wrote:
On Tue, Jun 11, 2013 at 2:08 AM, Axel Rauschmayer<axel at rauschma.de> wrote:
And it becomes harder to distinguish the interfaces, should you need to [1]. IMO, the purity argument also counts: this technique implies that iterators are a subset of iterables, but that isn’t true: For iterables, each invocation of
iterator
creates a new object, for iterators, it doesn’t. As noted in the first comment, it's trivial to check for the difference in all well-behaved cases by just seeing if "obj === objiterator" is true or false.That makes a terrible API, though. I think Axel has a valid point that the distinction between iterators and iterables is fuzzy enough to be more confusing and error-prone than useful.
Have you actually used Python or JS1.7 much, though?
Have to ask, since throwing "confusing" and "error-prone" charges demands evidence or at least anecdotes.
Sorry for the OT message.
On Tue, Jun 11, 2013 at 6:22 PM, Andrea Giammarchi <andrea.giammarchi at gmail.com> wrote:
We don't have interfaces ... I know, we could have mixins though, compatible with @@things too.
The concepts of Mixins has cropped up before in discussions and is used in current JS code ( EventEmitter ). I was wondering if it was possible to codify it in ES6 or is there simply no bandwidth/time for that?
class MyClass extends MySuper mixin EventEmitter, Iterable {}
Seems the sensible way to add it to the class syntax and it means you can still subtype and not use your one and only code-reuse slot ( extend ) for what is a mixin.
I personally +1 that but most likely they'll come back saying there is no time for that ... Object.mixin hasn't even been discussed in details ... however, it's in current ES6 draft so once that is defined, maybe it will be quick and easy to add mixin in the class? let's see
Brian Di Palma wrote:
Sorry for the OT message.
On Tue, Jun 11, 2013 at 6:22 PM, Andrea Giammarchi <andrea.giammarchi at gmail.com> wrote:
We don't have interfaces ... I know, we could have mixins though, compatible with @@things too.
The concepts of Mixins has cropped up before in discussions and is used in current JS code ( EventEmitter ). I was wondering if it was possible to codify it in ES6 or is there simply no bandwidth/time for that?
class MyClass extends MySuper mixin EventEmitter, Iterable {}
What would this syntax do that couldn't be done better by libraries? Remember that extends' RHS is an expression.
On Tue, Jun 11, 2013 at 5:33 PM, Brendan Eich <brendan at mozilla.com> wrote:
Brian Di Palma wrote:
Sorry for the OT message.
On Tue, Jun 11, 2013 at 6:22 PM, Andrea Giammarchi <andrea.giammarchi at gmail.com> wrote:
We don't have interfaces ... I know, we could have mixins though, compatible with @@things too.
The concepts of Mixins has cropped up before in discussions and is used in current JS code ( EventEmitter ). I was wondering if it was possible to codify it in ES6 or is there simply no bandwidth/time for that?
class MyClass extends MySuper mixin EventEmitter, Iterable {}
What would this syntax do that couldn't be done better by libraries? Remember that extends' RHS is an expression.
Don't wanna go off-topic of course, but: the the fact that RHS is an expression might you think Object.mixin(...) can be used there:
class Foo extends Object.mixin(...)
The problem with it as I mentioned (and which was successfully and unfortunately ignored ;)), it accepts only two arguments instead of variant to mix several objects.
But even it Object.mixin(...) could accept several arguments, we still can't extend a class with another class and a set of mixins.
class Bar extends Object.mixin(Foo, M1, M2...), where Foo is a class and Ms are mixin.
To do this you'll need to either express classes's implementation details (i.e. to break classes abstraction): class Bar extends Object.mixin(Foo.prototype, M1...);
Or, yeah, to provide a library method:
class Bar extends classWithMixins(Foo, M1, M2...);
(the later example is actually a real implementation we use in local projects currently).
Another issue with a library method -- it just looks immature for a mature language. From this viewpoint:
class Bar extends Foo { use M1, M2; }
looks better and mature as all normal OOP langs with mixins/traits. IMHO.
But as long as ES6 spec is already (almost) stable, I agree it can be taken to something ES7-ish.
Dmitry
if it's about being mature, then this is mature enough, if Object.mixin will be finalized:
class Bar extends [M1, M2, M3].reduce(
Object.mixin,
Object.create(Foo.prototype)
) {
// class definition ...
}
I must admit that looks weird though ...
br
On Jun 11, 2013, at 8:07 PM, Andrea Giammarchi wrote:
if it's about being mature, then this is mature enough, if Object.mixin will be finalized:
class Bar extends [M1, M2, M3].reduce( Object.mixin, Object.create(Foo.prototype) ) { // class definition ... }
I must admit that looks weird though ...
Exactly - it might be really cool from the language abilities perspective, but from the end-users, the example is not that useful: if the language provides some abstraction (the "class" abstraction in this particular case), the users which will join to JS starting form ES6 will have complete right to not even know what that magic "prototype" mean -- the one which you expose outside actually breaking the class abstraction. The prototype chain is just the implementation detail now (and all implementation detail should be hidden in the best), not a feature.
P.S.: and I still need to choose my English words better :) for the mature I probably more meant "syntactically solid" rather than a library method.
But yeah, I promised not to do big off-topic in this for-of thread.
Dmitry
A tempest in a teapot. We can have better APIs that look nice for this use-case. Develop some!
But let's not rush them into ES6, or invent custom syntax where API is enough.
If someone does fast and good work, there's still time for ES6, or 7 (doesn't matter as much as the quality of the work; browsers are prototyping parts of both).
Just for the record: Prototype lookup in ClassDefinitionEvaluation changed in draft rev. 14., so an API function should rather look like:
Mixin = (base, ...mixins) => mixins.reduce(Object.mixin, class extendsbase {}.prototype).constructor
At least users now no longer need to access .prototype manually:
js> class Foo { where(){ return "Foo" } }
js> const BarMixin = { where(){ return "BarMixin" } }
js> (new class extends Mixin(Foo) {}).where()
"Foo"
js> (new class extends Mixin(Foo, BarMixin) {}).where()
"BarMixin"
On 11 June 2013 21:19, Brendan Eich <brendan at mozilla.com> wrote:
Andreas Rossberg wrote:
That makes a terrible API, though. I think Axel has a valid point that the distinction between iterators and iterables is fuzzy enough to be more confusing and error-prone than useful.
Have you actually used Python or JS1.7 much, though?
Have to ask, since throwing "confusing" and "error-prone" charges demands evidence or at least anecdotes.
It is confusing me, for one -- I don't really understand the difference anymore, nor the intended use cases. Granted, I might be the only one confused. I'm not ready to believe that, though. ;)
You see, initially the iterator proposals tried to suggest a simple and understandable model: iterators were objects with a 'next' method, iterables were objects with an @@iterator method that produces an iterator. But neither is true anymore:
-
Iterators are iterables, they are supposed to have a 'next', optionally a 'throw', and an @@iterator method that returns itself. That is, the iterator interface has become considerably more complicated and is mutually intertwined with iterables.
-
Iterables are iterators most of the time, except when they aren't. Their @@iterator method may produce a new iterator or, in most cases, not. That is, there is no useful contract of what an iterable is, and it is useless as a concept to implement abstractions against. Nevertheless, people will probably try, and then build something that does not reliably work for all iterables. Thus error-prone.
Where things stand now, the only reason we have @@iterator at all is as a hook to provide implicit user-defined conversions from objects to iterators, invoked by the for-of loop (and by extension, yield*).
We could simplify spec and programmers' life by explicitly recognizing it as such. That is, for-of invokes a ToIterator conversion meta function, which is the identity for any 'nextable', and tries to invoke @@iterator for others. Thereby drop the misleading notion of iterable, and drop the weird requirement for iterators to be iterables themselves.
WDYT?
Le 12 juin 2013 à 12:55, Andreas Rossberg <rossberg at google.com> a écrit :
On 11 June 2013 21:19, Brendan Eich <brendan at mozilla.com> wrote:
Andreas Rossberg wrote:
That makes a terrible API, though. I think Axel has a valid point that the distinction between iterators and iterables is fuzzy enough to be more confusing and error-prone than useful.
Have you actually used Python or JS1.7 much, though?
Have to ask, since throwing "confusing" and "error-prone" charges demands evidence or at least anecdotes.
It is confusing me, for one -- I don't really understand the difference anymore, nor the intended use cases. Granted, I might be the only one confused. I'm not ready to believe that, though. ;)
You see, initially the iterator proposals tried to suggest a simple and understandable model: iterators were objects with a 'next' method, iterables were objects with an @@iterator method that produces an iterator. But neither is true anymore:
Iterators are iterables, they are supposed to have a 'next', optionally a 'throw', and an @@iterator method that returns itself. That is, the iterator interface has become considerably more complicated and is mutually intertwined with iterables.
Iterables are iterators most of the time, except when they aren't. Their @@iterator method may produce a new iterator or, in most cases, not. That is, there is no useful contract of what an iterable is, and it is useless as a concept to implement abstractions against. Nevertheless, people will probably try, and then build something that does not reliably work for all iterables. Thus error-prone.
Where things stand now, the only reason we have @@iterator at all is as a hook to provide implicit user-defined conversions from objects to iterators, invoked by the for-of loop (and by extension, yield*).
We could simplify spec and programmers' life by explicitly recognizing it as such. That is, for-of invokes a ToIterator conversion meta function, which is the identity for any 'nextable', and tries to invoke @@iterator for others. Thereby drop the misleading notion of iterable, and drop the weird requirement for iterators to be iterables themselves.
WDYT?
/Andreas
Well, as currently specced, the operation that serves as ToIterator
conversion meta function is:
itr => itr[@@iterator]()
If I understand you correctly, you propose that it should be changed to:
itr => @@iterator in itr ? itr[@@iterator]() : itr
The only place where it would simplify programmers' life, is where they try to define manually an iterator, which I expect to be quite rare.
On the other hand, I see a small advantage of the "weird" requirement for iterators to be iterables themselves: It allows to discriminate between true iterators and merely "nextable" objects.
―Claude
On Tue, Jun 11, 2013 at 12:53 PM, Allen Wirfs-Brock <allen at wirfs-brock.com>wrote:
But, really, for-of (and Arrray.from and friends) should just accept either an Iterable or an Iterator with priority given to Iterable
Then the above an most conveniently be coded as:
function giveMeAEmptyIterator( ) { return { next() {return {done: true}}, } }
More convenient still:
function *giveMeAEmptyIterator() {}
This will work fine even if for-of remains as currently specified (unconditionally calling the @@iterator method, as I think it should).
On Wed 12 Jun 2013 17:17, Jason Orendorff <jason.orendorff at gmail.com> writes:
function *giveMeAEmptyIterator() {}
Is this valid?
(I think it should be, FWIW)
This will work fine even if for-of remains as currently specified
This would work fine under any proposal currently under discussion, AFAIK.
Andy
On Jun 12, 2013, at 8:17 AM, Jason Orendorff wrote:
On Tue, Jun 11, 2013 at 12:53 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote: But, really, for-of (and Arrray.from and friends) should just accept either an Iterable or an Iterator with priority given to Iterable
Then the above an most conveniently be coded as:
function giveMeAEmptyIterator( ) { return { next() {return {done: true}}, } }
More convenient still:
function *giveMeAEmptyIterator() {}
This will work fine even if for-of remains as currently specified (unconditionally calling the @@iterator method, as I think it should).
-j
Yes, but the point was to illustrate what it looks like for folks who choose not to use a generator function. In general, I agree that using generators is usually what you want to do.
On 12 June 2013 14:12, Claude Pache <claude.pache at gmail.com> wrote:
Well, as currently specced, the operation that serves as
ToIterator
conversion meta function is:itr => itr[@@iterator]()
If I understand you correctly, you propose that it should be changed to:
itr => @@iterator in itr ? itr[@@iterator]() : itr
The only place where it would simplify programmers' life, is where they try to define manually an iterator, which I expect to be quite rare.
Maybe, but it's a simplification nevertheless. Moreover, removing a concept as such reduces cognitive load for everybody. Mind you, these advantages are minor.
On the other hand, I see a small advantage of the "weird" requirement for iterators to be iterables themselves: It allows to discriminate between true iterators and merely "nextable" objects.
And in the spirit of "duck typing", why would you want to do that? And how do you want to treat an iterable with a 'next' method, but whose @@iterator method does not return 'this'? Is that a "true" iterator? (The fact that such a beast can even exist shows how fuzzy those notions are.)
On Jun 12, 2013, at 8:30 AM, Andy Wingo wrote:
On Wed 12 Jun 2013 17:17, Jason Orendorff <jason.orendorff at gmail.com> writes:
function *giveMeAEmptyIterator() {}
Is this valid?
(I think it should be, FWIW)
Unless there are objections, I intend to "fix" this bug and make the occurrence of yield within a generator function optional. The restriction is really a carry over from the days before the * was added to the function definition. At that time the occurrence of yield in the body was what made a function definition a generator function definition.
Personally, I don't really think about what is going on here as a type coercion but I have no problem if you what to explain it as applying ToIterator. It may even be useful to use this in the spec.
The Iterator and Iterable interfaces that I currently have defined in the draft are not really used in a normative manner anywhere but I think they are a convenient short hand. Iterable is just shorthand for "has a @@iterator method". Iterator just means "has a "next" method that returns a iteration result object". Some objects are both an Iterable and an Iterator. I think of "throw" as part of the generator interface and yield as expecting either an iterable, an iterator or a generator as an argument.
To me the only substantive question we have on the table is whether "ToIterator" is defined as
ToIterator(obj) return the result of Invoke(obj,@@iterator,())
or ToIterator(obj) if obj has @@iterator return the result of Invoke(obj,@@iterator,()) else return obj
The spec. draft currently uses the equivalent of the first in for-of. I think changing it to the second would be fine.
Andy Wingo wrote:
function *giveMeAEmptyIterator() {}
Is this valid?
(I think it should be, FWIW)
It absolutely should be. See
"""
DaveH's presentation on using generators to write asynchronous code.
How do you compose generators? yield*
Waldemar: Given yield*, writing base-case trivial generators that don't yield becomes useful but its syntax is a problem. Generators should not be distinguished from functions by the presence of a yield statement.
"""
This led to the "function*" syntax for generators, to enable empty-bodied generators as the basis cases.
The @iterator part of the iteration protocol is not about coercion or types at all. It is a "pattern" for getting or creating a usable iterator from an object.
In this light, ToIterator is the wrong name. GetIterator (see C#) is better.
Le 12 juin 2013 à 18:14, Andreas Rossberg <rossberg at google.com> a écrit :
On 12 June 2013 14:12, Claude Pache <claude.pache at gmail.com> wrote:
<snip>
On the other hand, I see a small advantage of the "weird" requirement for iterators to be iterables themselves: It allows to discriminate between true iterators and merely "nextable" objects.
And in the spirit of "duck typing", why would you want to do that?
Just so that an error is surely thrown in case I feed a for/of loop with a non-iter{ator|able}. In the spirit of duck-typing, I consider that "having an @@iterator
method" is a more robust check than "having an @@iterator
or a next
method". Not a big deal, however.
And how do you want to treat an iterable with a 'next' method, but whose @@iterator method does not return 'this'? Is that a "true" iterator? (The fact that such a beast can even exist shows how fuzzy those notions are.)
You're right, but I don't expect to encounter such a beast often. Concretely, they are buggy objects that would not receive any special treatment (they are iterable whenever an @@iterator
method is expected, and they are iterated whenever a next
method is required).
–Claude
Claude Pache wrote:
Le 12 juin 2013 à 18:14, Andreas Rossberg<rossberg at google.com> a écrit :
On 12 June 2013 14:12, Claude Pache<claude.pache at gmail.com> wrote:
<snip>
On the other hand, I see a small advantage of the "weird" requirement for iterators to be iterables themselves: It allows to discriminate between true iterators and merely "nextable" objects. And in the spirit of "duck typing", why would you want to do that?
Just so that an error is surely thrown in case I feed a for/of loop with a non-iter{ator|able}. In the spirit of duck-typing, I consider that "having an
@@iterator
method" is a more robust check than "having an@@iterator
or anext
method". Not a big deal, however.
I think it is a medium-sized deal :-).
First, it's conceptually cleaner to have one symbol-named property test than two, symbol and then string name fallback.
Second, it is indeed more robust in this instance: 'next' is a common name, with many contextual meanings and likely uses.
And how do you want to treat an iterable with a 'next' method, but whose @@iterator method does not return 'this'? Is that a "true" iterator? (The fact that such a beast can even exist shows how fuzzy those notions are.)
You're right, but I don't expect to encounter such a beast often. Concretely, they are buggy objects that would not receive any special treatment (they are iterable whenever an
@@iterator
method is expected, and they are iterated whenever anext
method is required).
This just isn't a problem in practice. Let's solve real problems, look at cowpaths and nearby cliffs, not try to push boilerplate onto all programmers out of an off-target desire to make get- at iterator a "type coercion".
On 12 June 2013 19:51, Brendan Eich <brendan at mozilla.com> wrote:
The @iterator part of the iteration protocol is not about coercion or types at all. It is a "pattern" for getting or creating a usable iterator from an object.
You are entitled to call it what you think is best, of course, but in the current state of affairs, implicit coercion is the only thing @iterator actually gives you. Simple test: if you removed it from the picture (in favour of for-of just accepting iterators) that would be the only thing you'd lose.
On 12 June 2013 20:14, Brendan Eich <brendan at mozilla.com> wrote:
Claude Pache wrote:
Le 12 juin 2013 à 18:14, Andreas Rossberg<rossberg at google.com> a écrit :
On 12 June 2013 14:12, Claude Pache<claude.pache at gmail.com> wrote:
<snip>
On the other hand, I see a small advantage of the "weird" requirement for iterators to be iterables themselves: It allows to discriminate between true iterators and merely "nextable" objects.
And in the spirit of "duck typing", why would you want to do that?
Just so that an error is surely thrown in case I feed a for/of loop with a non-iter{ator|able}. In the spirit of duck-typing, I consider that "having an
@@iterator
method" is a more robust check than "having an@@iterator
or anext
method". Not a big deal, however.I think it is a medium-sized deal :-).
First, it's conceptually cleaner to have one symbol-named property test than two, symbol and then string name fallback.
Second, it is indeed more robust in this instance: 'next' is a common name, with many contextual meanings and likely uses.
I am a bit surprised to see you on the opposite side of the "duck typing" fence all of a sudden. ;)
And how do you want to treat an iterable with a 'next' method, but whose @@iterator method does not return 'this'? Is that a "true" iterator? (The fact that such a beast can even exist shows how fuzzy those notions are.)
You're right, but I don't expect to encounter such a beast often. Concretely, they are buggy objects that would not receive any special treatment (they are iterable whenever an
@@iterator
method is expected, and they are iterated whenever anext
method is required).This just isn't a problem in practice. Let's solve real problems, look at cowpaths and nearby cliffs, not try to push boilerplate onto all programmers out of an off-target desire to make get- at iterator a "type coercion".
Huh? The suggestion in question reduces boiler plate, namely the need for every iterator to implement a braindead @@iterator method. Nothing else changes in practical terms.
Andreas Rossberg wrote:
On 12 June 2013 19:51, Brendan Eich<brendan at mozilla.com> wrote:
The @iterator part of the iteration protocol is not about coercion or types at all. It is a "pattern" for getting or creating a usable iterator from an object.
You are entitled to call it what you think is best, of course, but in the current state of affairs, implicit coercion is the only thing @iterator actually gives you. Simple test: if you removed it from the picture (in favour of for-of just accepting iterators) that would be the only thing you'd lose.
Your rebuttal leaves it a matter of indifference what either of us calls it, but the "implicit" part is not at issue. The "coercion" is. You are taking a typed point of view, wanting something even most type systems can't express: that a fresh iterator is either always or never returned.
I think that's simply not useful based on copious experience in Python and JS.
On 12 June 2013 20:14, Brendan Eich<brendan at mozilla.com> wrote:
Claude Pache wrote:
Le 12 juin 2013 à 18:14, Andreas Rossberg<rossberg at google.com> a écrit :
On 12 June 2013 14:12, Claude Pache<claude.pache at gmail.com> wrote:
<snip>
On the other hand, I see a small advantage of the "weird" requirement for iterators to be iterables themselves: It allows to discriminate between true iterators and merely "nextable" objects. And in the spirit of "duck typing", why would you want to do that? Just so that an error is surely thrown in case I feed a for/of loop with a non-iter{ator|able}. In the spirit of duck-typing, I consider that "having an
@@iterator
method" is a more robust check than "having an@@iterator
or anext
method". Not a big deal, however. I think it is a medium-sized deal :-).First, it's conceptually cleaner to have one symbol-named property test than two, symbol and then string name fallback.
Second, it is indeed more robust in this instance: 'next' is a common name, with many contextual meanings and likely uses.
I am a bit surprised to see you on the opposite side of the "duck typing" fence all of a sudden. ;)
Please, no false dichotomies. I wrote on this thread yesterday that duck-typing via a symbol is more robust than via a string-name. It's still duck typing, though.
And how do you want to treat an iterable with a 'next' method, but whose @@iterator method does not return 'this'? Is that a "true" iterator? (The fact that such a beast can even exist shows how fuzzy those notions are.) You're right, but I don't expect to encounter such a beast often. Concretely, they are buggy objects that would not receive any special treatment (they are iterable whenever an
@@iterator
method is expected, and they are iterated whenever anext
method is required). This just isn't a problem in practice. Let's solve real problems, look at cowpaths and nearby cliffs, not try to push boilerplate onto all programmers out of an off-target desire to make get- at iterator a "type coercion".Huh? The suggestion in question reduces boiler plate, namely the need for every iterator to implement a braindead @@iterator method.
Yes but at the price of every for-of on an array, e.g., to call a values() iterator constructor. Count the right beans. Implementors of iterators are relatively few, especially given generators.
Failing to scale arguments about boilerplace costs by target victim-audience size is a capital mistake.
Le 12 juin 2013 à 20:46, Andreas Rossberg <rossberg at google.com> a écrit :
On 12 June 2013 19:51, Brendan Eich <brendan at mozilla.com> wrote:
The @iterator part of the iteration protocol is not about coercion or types at all. It is a "pattern" for getting or creating a usable iterator from an object.
You are entitled to call it what you think is best, of course, but in the current state of affairs, implicit coercion is the only thing @iterator actually gives you. Simple test: if you removed it from the picture (in favour of for-of just accepting iterators) that would be the only thing you'd lose.
Given that:
- an iterator can be used in only one for/of loop;
- an iterable (by which I mean primarily, an iterable non-iterator) can typically be reused in other for/of loops, including nested ones;
when I write
for (item of mySet)
, I consider that what I've written on the RHS of the 'of' keyword is definitely a (non-iterator) iterable—since I can reuse it two lines below—, and not something coerced to an iterator—that I couldn't reuse.
Therefore, I think that "For/of coerces its argument to an iterator" is a wrong mental model, and "For/of accepts either a reusable iterable or a disposable iterator" is a better one. And the implicit invocation of @@iterator
is better thought as Get-the-snark-I-need-for-doing-my-work, than as Coerce-to-a-disposable-object. (And the fact that iterators are also non-reusable iterables is an implementation detail that don't disturb my mental model, because I don't care, thanks to generators.)
On 12 June 2013 21:00, Brendan Eich <brendan at mozilla.com> wrote:
Andreas Rossberg wrote:
On 12 June 2013 19:51, Brendan Eich<brendan at mozilla.com> wrote:
The @iterator part of the iteration protocol is not about coercion or types at all. It is a "pattern" for getting or creating a usable iterator from an object.
You are entitled to call it what you think is best, of course, but in the current state of affairs, implicit coercion is the only thing @iterator actually gives you. Simple test: if you removed it from the picture (in favour of for-of just accepting iterators) that would be the only thing you'd lose.
Your rebuttal leaves it a matter of indifference what either of us calls it, but the "implicit" part is not at issue. The "coercion" is. You are taking a typed point of view, wanting something even most type systems can't express: that a fresh iterator is either always or never returned.
I'm not sure where you took that from. What does it matter what type systems can express?
This just isn't a problem in practice. Let's solve real problems, look at cowpaths and nearby cliffs, not try to push boilerplate onto all programmers out of an off-target desire to make get- at iterator a "type coercion".
Huh? The suggestion in question reduces boiler plate, namely the need for every iterator to implement a braindead @@iterator method.
Yes but at the price of every for-of on an array, e.g., to call a values() iterator constructor. Count the right beans. Implementors of iterators are relatively few, especially given generators.
Failing to scale arguments about boilerplace costs by target victim-audience size is a capital mistake.
There must be some confusion here. Yes, I said earlier that I wouldn't mind dropping the implicit magic altogether, but that isn't what the current subthread is about, where I suggested another alternative, which is (somewhat) simplifying the current model by viewing @iterator as an implicit conversion. That doesn't imply any of the above.
On 12 June 2013 23:27, Claude Pache <claude.pache at gmail.com> wrote:
Therefore, I think that "For/of coerces its argument to an iterator" is a wrong mental model, and "For/of accepts either a reusable iterable or a disposable iterator" is a better one. And the implicit invocation of
@@iterator
is better thought as Get-the-snark-I-need-for-doing-my-work, than as Coerce-to-a-disposable-object. (And the fact that iterators are also non-reusable iterables is an implementation detail that don't disturb my mental model, because I don't care, thanks to generators.)
In other words, you want to program with iterables. That is a perfectly reasonable thing to want. But unfortunately, with the current intermingled protocol, there is no particularly useful contract of what an iterable actually does (i.e., where the state goes). Consequently, if you want to write abstractions, you will likely be better off basing them on iterators, and leave iterables as a mere convenience mechanism for for-of loops over concrete objects.
Andreas Rossberg wrote:
Yes, I said earlier that I wouldn't mind dropping the implicit magic altogether, but that isn't what the current subthread is about, where I suggested another alternative, which is (somewhat) simplifying the current model by viewing @iterator as an implicit conversion. That doesn't imply any of the above.
Somehow I'm missing one of your messages -- sorry about that (fetching mail on a phone sometimes leaves it unfiltered in my Inbox).
But I've argued against "viewing @iterator as an implicit conversion", and so has Claude, so I think we are going around the block again.
Andreas Rossberg wrote:
On 12 June 2013 23:27, Claude Pache<claude.pache at gmail.com> wrote:
Therefore, I think that "For/of coerces its argument to an iterator" is a wrong mental model, and "For/of accepts either a reusable iterable or a disposable iterator" is a better one. And the implicit invocation of
@@iterator
is better thought as Get-the-snark-I-need-for-doing-my-work, than as Coerce-to-a-disposable-object. (And the fact that iterators are also non-reusable iterables is an implementation detail that don't disturb my mental model, because I don't care, thanks to generators.)In other words, you want to program with iterables. That is a perfectly reasonable thing to want. But unfortunately, with the current intermingled protocol, there is no particularly useful contract of what an iterable actually does (i.e., where the state goes).
No, the contract is perfectly useful and minimal. An array is iterable, it has @iterator.
Consequently, if you want to write abstractions, you will likely be better off basing them on iterators, and leave iterables as a mere convenience mechanism for for-of loops over concrete objects.
This does not follow. It flies in the face of boatloads of experience with JS and Python (itertools and beyond).
I guess we are going 'round the assertion block. I've seen that building before :-|.
Le 13 juin 2013 à 03:33, Brendan Eich <brendan at mozilla.com> a écrit :
Andreas Rossberg wrote:
On 12 June 2013 23:27, Claude Pache<claude.pache at gmail.com> wrote:
Therefore, I think that "For/of coerces its argument to an iterator" is a wrong mental model, and "For/of accepts either a reusable iterable or a disposable iterator" is a better one. And the implicit invocation of
@@iterator
is better thought as Get-the-snark-I-need-for-doing-my-work, than as Coerce-to-a-disposable-object. (And the fact that iterators are also non-reusable iterables is an implementation detail that don't disturb my mental model, because I don't care, thanks to generators.)In other words, you want to program with iterables. That is a perfectly reasonable thing to want. But unfortunately, with the current intermingled protocol, there is no particularly useful contract of what an iterable actually does (i.e., where the state goes).
No, the contract is perfectly useful and minimal. An array is iterable, it has @iterator.
Yes, the name of the contract is @@iterator
. A for/of loop is normally used to loop over an entire collection of items, so it is expected that the user provides an object from which one could extract a fresh (i.e. non-consumed) iterator. In order to enforce that requirement, one could ask iterators to remove or poison-pill its own @@iterator
method as soon as its next
method is called. That would add extra value (better error-checking) to the programmer, if we find it worth. But replacing @@iterator
with ToIterator
is mostly changing an implementation detail.
Consequently, if you want to write abstractions, you will likely be better off basing them on iterators, and leave iterables as a mere convenience mechanism for for-of loops over concrete objects.
This does not follow. It flies in the face of boatloads of experience with JS and Python (itertools and beyond).
I guess we are going 'round the assertion block. I've seen that building before :-|.
/be
If I want to write an abstraction in a library which loops over an entire collection (e.g,, an Iter.reduce
function), I just invoke the @@iterator
method to get an assumed fresh iterator (and it is the responsibility of the user to not provide me with a consumed or half-consumed iterator, unless we add the error-check I mentioned above). If we replace the @@iterator
method call by an abstract ToIterator
or GetIterator
operation, there should be a, say, Reflect.ToIterator
function that I could use in my library code.
―Claude
Claude Pache wrote:
Yes, the name of the contract is
@@iterator
. A for/of loop is normally used to loop over an entire collection of items, so it is expected that the user provides an object from which one could extract a fresh (i.e. non-consumed) iterator. In order to enforce that requirement, one could ask iterators to remove or poison-pill its own@@iterator
method as soon as itsnext
method is called.That would add extra value (better error-checking) to the programmer, if we find it worth.
I've never felt the need, in JS1.7+ or Python.
But replacing
@@iterator
withToIterator
is mostly changing an implementation detail.
Agreed, but see below.
Consequently, if you want to write abstractions, you will likely be better off basing them on iterators, and leave iterables as a mere convenience mechanism for for-of loops over concrete objects.
This does not follow. It flies in the face of boatloads of experience with JS and Python (itertools and beyond).
I guess we are going 'round the assertion block. I've seen that building before :-|.
/be
If I want to write an abstraction in a library which loops over an entire collection (e.g,, an
Iter.reduce
function), I just invoke the@@iterator
method to get an assumed fresh iterator (and it is the responsibility of the user to not provide me with a consumed or half-consumed iterator, unless we add the error-check I mentioned above). If we replace the@@iterator
method call by an abstractToIterator
orGetIterator
operation, there should be a, say,Reflect.ToIterator
function that I could use in my library code.
No plan to replace -- the public symbol is simpler and avoids growing traps and Reflect default-trap-impls like topsy.
I can’t find the rationale as to why that isn’t allowed. Is it because iterators can’t be recognized as safely as iterables?
If we did enable for-of (and Array.from) for iterators, we would get two advantages:
this
) to work with for-of.Axel