tc39 / proposal-iterator-helpers

Methods for working with iterators in ECMAScript
https://tc39.es/proposal-iterator-helpers
1.33k stars 35 forks source link

Consider making the library for Iterables rather than Iterators #8

Closed Jamesernator closed 5 years ago

Jamesernator commented 5 years ago

Generally when working with iterables it is nicer to be able to use the result of applying combinators multiple times if the source iterable is re-usable.

For example suppose we have a custom combinator repeat:

function* repeat(iterable, times) {
  for (let i = 0; i < times; i++) {
    yield* iterable
  }
}

// Using the proposed spec's Iterator.map

const seq = Iterator.of(1,2,3)
  .map(x => x**2)

repeat(seq, 2).collect() // [1,4,9] Which isn't really expected

The solution to this is to wrap everything in a [Symbol.iterator]() method:

function repeat(iterable, times) {
  // Where iterable.from creates an object that wraps the
  // Iterable.prototype correctly
  return Iterable.from({
    * [Symbol.iterator]() {
      for (let i = 0; i < times; i++) {
        yield* iterable
      }
    }
  })
}

// Iterable.prototype.map = function map(mapperFn) {
//   return Iterable.from({
//     * [Symbol.iterator]() {
//       for (const item of this) {
//         yield mapperFn(item)
//       }
//     }
//   })
// }

const seq =  Iterable.of(1,2,3)
  .map(x => x**2)

repeat(seq, 2).collect() // [1, 4, 9, 1, 4, 9] as expected

However this is quite tedious, I actually implemented this pattern in my own library and it prevents a lot of footguns especially when implementing more specialised operators.

devsnek commented 5 years ago

The iterable protocol is just something that defines how to get an iterator. Iterators themselves are the interface you work with to consume data. Because of this I have to disagree with the approach you take here.

However, I think adding an Iterator.prototoype.repeat would be reasonable, and in fact it is already listed in the prior art section of the readme.

ljharb commented 5 years ago

By convention, most iterators are iterable (their Symbol.iterator method does return this), and by convention, builtin APIs tend to operate on iterables and not iterators directly.

Jamesernator commented 5 years ago

I don't know I'd agree that iterators are what you'd want to work with though. Ultimately it's probably going to be consumed by a for-of (or for-await-of loop), which cannot consume iterators, they consume iterables.

Working with iterators directly can result in footguns too, for example consider an implementation of repeat that only takes an iterator rather than an iterable (which is what the method form is like):

function* repeat(iterator, times) {
  if (times === 0) {
    return
  }

  const stored = []
  while (true) {
    const { value, done } = iterator.next()
    if (done) return
    stored.push(value)
    yield value
  }

  for (let i = 0; i < times - 1; i++) {
    yield* stored
  }
}

We effectively have to store another copy of the values from the iterator, however this isn't good, consider Iterator.range(0, 1000000).repeat(2), this would needlessly create an array with 1000000 items whereas the purely iterable solution of:

Iterable.range = function range(min, max) {
  return {
    __proto__: Iterable.prototype,
    * [Symbol.iterator]() {
      for (let i = min; i < max; i++) {
        yield i
      }
    }
  }
}

const twiceRange = Iterable.range(0, 1000000).repeat(2)

Would not suffer this solution, because once the repetition is done once, it throws the iterator away and creates a new one, in constrast to the purely iterator form which needs to tee anything it might ever want to use more than once.

devsnek commented 5 years ago

you don't know that creating the iterator twice will yield the same items every time for repeat. you can't use that technique there. also... you can use iterators with for..of loops.

Jamesernator commented 5 years ago

No iterators cannot be consumed by for-of loops, Iterator !== iterator:

const iterable = {
  [Symbol.iterator]() {
    let i = 0
    return {
      next() {
        i += 1
        if (i > 3) {
          return { done: true }
        }
        return { done: false, value: i }
      }
    }
  }
}

// Prints 3 numbers
for (const i of iterable) {
  console.log(i)
}

const iterator = iterable[Symbol.iterator]()

// Throws an error
for (const i of iterator) {
  console.log(i)
}

The fact that IteratorPrototype returns this is a convenience method, nothing about the Iterator protocol requires an Iterator to be iterable.


Regarding repeat, my repeat above is closer to itertools.flatten(itertools.repeat(someIterable, 2)) in Python than to itertools.cycle(someIterable).

I'm not sure that there's really a clear intuition to what a name like repeat should do. In my case perhaps a better name would've been flatRepeat.

But even so, it doesn't resolve the range case, if we want a flatRepeat we can't do that without tee-ing in an iterator library.


I don't really see the benefit of an iterator proposal rather than an iterable one. Every single method you could define for an iterator has an equivalent iterable one, for example the common cycle function in itertools libraries could easily be written as:

Iterable.prototype.cycle = function(iterable, times=Infinity) {
  return {
    * [Symbol.iterator]() {
      if (times === 0) {
        return
      }
      let copy = []
      for (const item of iterable) {
        copy.push(item)
        yield item
      }
      for (let i = 0; i < times - 1; i++) {
        yield* copy
      }
    }
  }
}

However we can't do the reverse, there is no similarly efficient version of flatRepeat for an iterator library, we always have to tee the iterator.

This just seems bad having two ways of doing things when people care about iterables not iterators. The only reason the upper majority of people use builtin iterators like array.entries() or the like is because they also happen to be iterable.

devsnek commented 5 years ago

@Jamesernator you get an error because you don't inherit from %IteratorPrototype%, which includes [Symbol.iterator]() { return this; } (see the second example in https://github.com/devsnek/proposal-iterator-helpers#example-usage)

You're right that it's not part of the protocol itself, but anyone exposing an iterator manually should do this by extending %IteratorPrototype%. everything in the js builtins does this, everything on the web platform does this, everything in node does this, etc. The reason this proposal exposes Iterator.prototype is because you should do this too.

I don't really see the benefit of an iterator proposal rather than an iterable one

Iterables are just something with Symbol.iterator. All this means is that they can expose a consumable stream of information. Something that is iterable does not have the interface of dealing with the actual stream of information, but rather the api for whatever that iterable does. As an example, arrays, maps, and sets are all iterable, but having a .map for operating as an iterable doesn't match their first class API.

Jamesernator commented 5 years ago

I will also point out another example of a library that really hasn't been considered in the discussion: RxJS.

While RxJS operators on Observable they have a large number of similarities with iterables. In both structures we have a sequence of values we can consume in some way and would probably want to apply operators over.

In RxJS you do not apply methods to the subscriber (which is analagous to an iterator) even though you could if you wanted to e.g.:

function map(subscriber, mapperFn) {
  return {
    next(value) {
      subscriber.next(mapperFn(value))
    },

    error(err) {
      subscriber.error(err)
    },

    complete() {
      subscriber.complete()
    },
  }
}

const subscriber = {
  next(i) { console.log(i) }
}

someObservable.subscribe(map(subscriber, i => i **2))

Instead you apply the methods to the Observable (analagous to Iterable).

In fact checking it, RxJS even provides a repeat function like I was describing (which is probably where I remember it from as I heavily used Observable in the past). This repeat function consumes the Observable twice, not once replaying the result.

ljharb commented 5 years ago

@devsnek right but for..of takes iterables, not iterators, it just happens that builtin iterators all are also iterable.

devsnek commented 5 years ago

so are the ones on the web platform, node, and anything created by generators. The only discrepancy is when you write out your own object with next/return/throw.

If the problem here is the definition of the iterator interface, i'm happy also make that more explicit.

ljharb commented 5 years ago

Sorry, what i mean is, i agree that it would be foolish to make a non-iterable iterator - I’m saying that it’s maximally useful to make operations that work on iterables and not just iterators.

devsnek commented 5 years ago

These also work on iterables... the big constraint here is that the iterable objects have their own first class apis (like array or ReadableStream) so it would make no sense to coerce them into also supporting an entire set of methods to operate on iterators.

ljharb commented 5 years ago

By “work on iterables” it would probably be sufficient if the first thing it did is GetIterator on the iterable (which might already be an iterator), and then operated only on the resulting iterator.

rbuckton commented 5 years ago

I'm generally opposed to extending %IteratorPrototype% for this, I feel it is the wrong extensibility point as iterators are consuming, i.e. once you run an iterator to completion it cannot be re-iterated. Prior art like .NET's System.Linq.Enumerable operate at the IEnumerable level (equivalent to an ECMAScript "iterable"), as do libraries like lodash and itertools (and my own iterable-query package). Unfortunately, %GeneratorPrototype% suffers from the same issues as %IteratorPrototype%.

I have written a fair amount of code that depends on querying "iterables" that would not work for "iterators". As a result, I most likely wouldn't be able to use this feature if it were to make it into the language and I would still need to depend on 3rd party libraries.

In general I would prefer to see these as functions that could be used in conjunction with either the pipeline operator (|>) or bind operator (::) proposals, or barring that an Iterable base-class or "mix-in" that could be used with built-ins and userland code, even if that means waiting on this proposal (or a variant of this proposal) until one of those options becomes standardized.

devsnek commented 5 years ago

@rbuckton can you just outline quickly what the semantics look like? my understanding is that for every iterator you then need to create this "iterable" wrapper around it which you then chain methods off of. is this correct?

the reason i'm hesitant about this is because of the interaction with iterators that i've seen in the ecosystem. Things tend to expose one-shot iterators, rather than iterables (generators, map.prototype.keys, web and node apis, etc, all work like this)

I'm very open to changing this, but personally i'm still unconvinced that the change makes sense for javascript, regardless of what other languages do.

rbuckton commented 5 years ago

@devsnek: For this proposal to work, you have to create an "iterator" wrapper around it as well, so it is not much different.

For example, consider filter and filterBy in iterable-query:

For the pipeline proposal (F# with partial application), you would use filter like this:

import { fn } from "iterable-query";
const odds = [1, 2, 3] |> fn.filter(?, x => x % 1 === 1);
for (const x of odds) console.write(x);
const sum = odds |> fn.sum();
console.log(sum);

If not using pipeline, you can do the same thing with a Query:

import { from } from "iterable-query";
const odds = from([1, 2, 3]).filter(x => x % 1 === 1);
for (const x of odds) console.write(x);
const sum = odds.sum();
console.log(sum);

// alternatively:
// import { Query } from "iterable-query";
// const odds = Query.from([1, 2, 3]).filter(x => x % 1 === 1);
// ...

If we were using iterator, sum would be 0. While this is a somewhat contrived case, I have real-world code that depends on being able to restart iteration.

If map.keys()[Symbol.iterator]() had been specified to return a fresh iterator rather than this, I might be more supportive of this proposal. Unfortunately, that would be a breaking change. Perhaps we could add something like a @@newIterator method that these methods could look at first if it existed, and fall back to @@iterator if it did not? @@newIterator would be expected to always return a fresh iterator, and to either not exist or perhaps return null if such freshness is not possible? That might remove some of my concerns.

domenic commented 5 years ago

Upon reflection, this issue is basically what I was getting at with #5, and with the introduction to the TC39 presentation which Ron attended. JavaScript has iterators; they are here to stay, and are useful, and many of us are able to use them without encountering all the issues that some folks here describe. This proposal is about making them more useful.

Separately, folks should also work on a proposal to make the language's various iterables (so far Map, Set, Array) more useful. Those are good things to do: as mentioned, it's much nicer to be able to do const newMap = myMap.filter(entry => ...) than to have to do const newMap = new Map(myMap.entries().filter(entry => ...)).

I definitely encourage people to work on such a proposal for iterables. However, this proposal is focused on making iterators more useful. I think it would be a huge shame if folks blocked work on improving iterators, because in their code they prefer not to operate on them. I would rather have complementary efforts that do not block each other.

devsnek commented 5 years ago

@rbuckton if you create your iterable from a given stateless resource such as an array that works fine, but the most common case is that all you have is an iterator, such as from invoking a generator or calling Map.prototype.keys.

function* someNumbers() {
  let i = 0;
  while (i < 100) yield i++;
}

const odds = Iterable.from(someNumbers()).filter((x) => x % 1 === 1);
for (const x of odds) console.log(x); // 1, 3, 5, ...
console.log(odds.sum()); // still 0?

If map.keys()[Symbol.iterator]() had been specified to return a fresh iterator rather than this, I might be more supportive of this proposal.

I feel exactly the opposite. If it returned a fresh one I would want your API, but because it doesn't i think the prototype api makes more sense.

rbuckton commented 5 years ago

Out of curiosity, how web-incompatible would it be to have Array, Map, and Set inherit from an Iterable base class/%IterablePrototype% prototype?

domenic commented 5 years ago

I would be very surprised (but pleasantly so) if making changes to Array was doable at all.

rbuckton commented 5 years ago

If we use %IteratorPrototype% for this, my only alternative will be to do this:

const odds = () => [1, 2, 3].values().filter(x => x % 1 === 1);
for (const odd of odds()) console.log(odd);
const sum = odds().sum();
console.log(sum);

It's not too terrible, but I'm still not a big fan.

For a generator function, you are correct that it would always be consuming regardless. Most of the times I've implemented a generator as part of an API, however, its been something like this:

class C {
 *[Symbol.iterator]() {
  yield 1;
  yield 2;
 }
}
const obj = new C();
for (const x of obj) ...
for (const x of obj) ... // restarts iteration from the beginning.

The fact that the majority of the prior art linked in the explainer point to APIs that work at the "iterable" level seems to be a strong indicator that "iterator" is the wrong place.

devsnek commented 5 years ago

@rbuckton the prior art exists to suggest that there is a precedent of working with language constructs that represent an ordered pull stream of values.

in javascript specificlly, as you have helpfully demonstrated, this happens to usually be the iterator. people don't write class c { *[Symbol.iterator]() { ... } } they write function* c() { ... }. Whether that was a good or bad design choice is definitely out of scope of this proposal.

rbuckton commented 5 years ago

The fact that you can create a generator that produces a consuming iterator isn't the issue (i.e. the design choice was fine). C# works effectively the same way:

class C : IEnumerable { // new C() is Enumerable
  public IEnumerator GetEnumerator() { // analogous to [Symbol.iterator]
    yield return 1;
    yield return 2;
  }
  …

  IEnumerable CustomEnumerator() { // analogous to function*
    yield return 1;
    yield return 2;
  }
}

Right now the C# and ES implementations (with respect to iteration only) are roughly the same. The place where they would diverge is if we choose to add these methods to %IteratorPrototype% rather than some other construct.

devsnek commented 5 years ago

@rbuckton Right.... I'm adding things to the enumerator here, not the enumerable. That's the same in C#. All the helpers in C# are on IEnumerator.

rbuckton commented 5 years ago

@devsnek: That is incorrect. All helpers in C# are for IEnumerable.

For example: System.Linq.Enumerable.Select - https://docs.microsoft.com/en-us/dotnet/api/system.linq.enumerable.select?view=netframework-4.7.2#System_Linq_Enumerable_Select__2_System_Collections_Generic_IEnumerable___0__System_Func___0_System_Int32___1__

devsnek commented 5 years ago

@rbuckton it seems we were looking at different interfaces. I'm looking at the more generic System.Collections.IEnumerable and System.Collections.IEnumerator.

In the case of System.Linq.*, I disagree that the design of those matches the design of what js has, so i think its kind of irrelevant to this case. If JS was designed like System.Linq, setInstance.map would be an iteration helper, which just isn't how things work in this ecosystem.

Again, I don't think the overarching design of iteration in JS is within scope here.

Jamesernator commented 5 years ago

I will point out something else that could be done that would allow the same methods to be used on both iterators and iterables would be something like the protocols proposal.

If we have an abstract method that needs to be implemented (e.g. [Iterable.toType] then we could just call that with the result of applying a given method to an iterator. All Iterable's could then just implement that protocol.

As a concrete example supposing Array/Iterator/Set/etc were classes:

class Array implements Iterable {
  [Iterable.toType](iterableIterator) {
    return Array.from(iterableIterator)
  }
}

class Set implements Iterable {
  [Iterable.toType](iterableIterator){
    return new Set(iterableIterator)
  }
}

class Iterator implements Iterable {
  [Iterable.toType](iterable) {
    return iterable[Symbol.iterableIterator]()
  }
}

The implementation of methods would look something like this:

function* _mapImplementation(iterable, mapperFn) {
  for (const item of iterable) {
    yield mapperFn(item)
  }
}

function* _repeatFlatImplementation(iterable, times) {
  for (let i = 0; i < times; i++) {
    yield* iterable
  }
}

const Iterable = makeProtocolSomehow({
  requiredMethods: {
    toType: Symbol('Iterable.toType'),
  },
  methods: {
    map(mapperFn) {
      return this[Iterable.toType](_mapImplementation(this, mapperFn))
    },

    repeat(this) {
      return this[Iterable.toType](_repeatFlatImplementation(this, times))
    },
  },
}

Not only would the methods just work on Iterator but they also would work on all current and future iterables.

rbuckton commented 5 years ago

We are not looking at different interfaces, per se.

An IEnumerable in .NET is not required to return a fresh IEnumerator, but most built-in collections do. System.Linq.Enumerable interacts with IEnumerable and supports iterating over "fresh" IEnumerator instances of the source if they are provided, as that is inherent in the call to GetEnumerator (nee @@iterator in ECMAScript).

If System.Linq.* is irrelevant, it shouldn't be listed as Prior Art. However, I strongly believe that it is relevant to this discussion and the proposal in general.

rbuckton commented 5 years ago

@Jamesernator: That goes to my point that a "mixin" (or protocol) approach seems like a better alternative to me than putting these on %IteratorPrototype%.

devsnek commented 5 years ago

@rbuckton again, prior art is listed to show the usage of working with ordered pull streams of data in other languages. For example, i linked rust, but this proposal is not suggesting that iter::Peekable should be added (although it would be cool if it was, idea for another proposal there 😄).

I'm really confused about how you're viewing the usage of existing apis within the ecosystem, could you provide some examples of things like creating iterators and using iterators and etc under your view?

rbuckton commented 5 years ago

AFAIK, existing 3rd-party libraries that work with ECMAScript iterables/iterators are leveraging the fact that these objects have an @@iterator method. That method can return a fresh Iterator or the same Iterator (in the case of %IteratorPrototype% and generator functions). They work for function* simply because the result is both an Iterator and is "iterable".

How would you implement %IteratorPrototype%.filter? I see one of two options:

// option 1
%IteratorPrototype%.filter = function* (predicate) {
  for (const value of this) {
    if (predicate(value)) yield value;
  }
};

// option 2
%IteratorPrototype%.filter = function* (predicate) {
  for (let { value, done } = this.next(); !done; { value, done } = this.next()) {
    if (predicate(value)) yield value;
  }
};

If it is option 1 above, then you are actually leveraging the fact that the Iterator must be "iterable". The fact that spread, destructuring, Array.from, and for..of operate on the "iterable" and not the Iterator is, to me, a strong indicator that it is the "iterable" that should be what is used for these operations.


After giving it some thought, I have an alternative proposal that meshes with the current proposal that might be more agreeable to me:

rbuckton commented 5 years ago

Note that, with my suggested change, Array, Map, and Set would not get these methods automatically. Instead we could have Iterable(obj) (or Iterable.from(obj), etc.) wrap them in an "iterable" that does have %IterablePrototype% in its prototype chain, and just forwards @@iterator to obj[@@iterator].

devsnek commented 5 years ago

a strong indicator that it is the "iterable" that should be what is used for these operations.

its very purposeful that %IteratorPrototype% already has Symbol.iterator on it :)

--

After giving it some thought ...

This looks mostly like how i imagine people would work with this in an fp way, modulo some semantics we can bikeshed later. have we reached the conclusion?

domenic commented 5 years ago

I have an alternative proposal that meshes with the current proposal that might be more agreeable to me:

I don't think we should attempt to shoehorn C# semantics into JavaScript in this way. The committee has already gained consensus against this, first during the ES2015 process when the iterable/iterator protocol was established, and then later when the observable proposal champions were trying to claim that the current design was a mistake.

I think the best thing to do is, as I mentioned above, separately pursue ways to make iterables more useful, while letting this proposal stay more narrowly focused on iterators. I understand that for those who only want to program C#-style, that means they may not be able to use this proposal. That's OK; not every proposal addresses every programmer.

rbuckton commented 5 years ago

have we reached the conclusion?

Possibly. I think my concerns are based on the fact that %IteratorPrototype% is called "Iterator" and its @@iterator method returns this. If it had instead been an %IterablePrototype% whose @@iterator method throws a TypeError, I would have been less concerned.

I don't think we should attempt to shoehorn C# semantics into JavaScript in this way.

That's not what I'm trying to do here. In fact, I think the root of my concern is more to do with the perceived purpose of %IteratorPrototype%, as I mention at the top of this comment. Everything in the ECMAScript spec today that inherits from %IteratorPrototype% is in reality both an Iterator and and Iterable. The %IteratorPrototype% itself doesn't actually implement the Iterator interface at all.

Having an %IterablePrototype% whose @@iterator throws by default provides an extensibility point for custom collections (especially since calling %IterablePrototype%.prototype[@@iterator].call(obj) would be effectively useless anyways), while helping to ensure that developers write correct subclasses (i.e. implement their own @@iterator that does something useful). This is an approximation of an abstract method. Having a public Iterable extensibility point that I can subclass to get this behavior without also requiring that I implement next (or return/throw) would be important to me, because the things I want to be able to iterate over may not themselves be iterators (such as with Array/Map/Set today).

rbuckton commented 5 years ago

TLDR: We pretty much have the right semantics already (and they are mostly aligned with C# in any case), we just have the wrong name.

domenic commented 5 years ago

There are indeed many ways to make Map/Set/Array have more methods, including introducing a new prototype and hoping it's web compatible. I encourage a separate proposal to explore that, or for you to collaborate with the champions of https://github.com/tc39/proposal-collection-methods.

ljharb commented 5 years ago

Separate from the concerns discussed above; I don't see why we'd want to break with consistency and operate on iterators rather than iterables - is there any builtin API that takes an iterator but not an iterable (given that all builtin iterators are themselves iterable, and GetIterator is used on them)?

domenic commented 5 years ago

I agree that APIs that accept iterables should continue to do so. And in general, APIs that accept "sequencey things" should take iterables!

I don't see any break in consistency though; this API is about making iterators more useful, and doesn't introduce any new methods that take "sequencey things".

rbuckton commented 5 years ago

@domenic: I think you may be misunderstanding my point. I'm not necessarily advocating for that, just that we're using the wrong terminology/names here. The end result is effectively the same (i.e. map.keys().filter() would work), I just don't want this:

class C extends WhateverWeCallThis {
  [Symbol.iterator]() { … }
}

to imply that C has a next() method, since C is not an "Iterator". If the global is called Iterable instead of Iterator, and the prototype is %IterablePrototype% rather than %IteratorPrototype% then I'm happy.

@ljharb: As I said, I think the reality is that these methods do operate on iterables (since %IteratorPrototype% is itself iterable), its just poorly named.

domenic commented 5 years ago

Right, this proposal is not about adding new methods to iterables, it is about adding new methods to iterators. So the C in your example, an iterable, would not gain any new methods from this proposal---that's for you to do in a separate proposal, as I mention. It's only the return value of (new C())[Symbol.iterator]() that would get any new methods in this proposal.

ljharb commented 5 years ago

Adding new methods only to iterators makes sense to me - I'd still expect them to call GetIterator on their receiver, making them technically operate "on iterables".

rbuckton commented 5 years ago

In TypeScript's definitions we use the following terminology:

domenic commented 5 years ago

@ljharb I don't think iterators should add a pointless step that gets this via an indirection, instead of operating on it directly.

None of the current methods on any of the spec's iterators (e.g. next() or throw() or return()) do a GetIterator() call on their thisArg. They just operate on their thisArg directly.

rbuckton commented 5 years ago

@domenic: I think they must use this indirection step, because I'd love to be able to have an Iterable that uses these features that is not an Iterator. The @@iterator is the protocol that indicates that the thing returned is the iterator.

domenic commented 5 years ago

Yes, I understand you'd love to use iterables. But this proposal is about iterators, not iterables, so since we're not trying to satisfy that goal, there's no "must" there.

As I've repeatedly said, to add more methods to other iterables, you should work on a different proposal.

rbuckton commented 5 years ago

You don't have to do a GetIterator in next() because you are the iterator. Despite its name, %IteratorPrototype% is not an iterator, since it doesn't have a next() method.

domenic commented 5 years ago

IteratorPrototype is an "abstract class" in that sense; it has a "pure virtual" next method. All concrete instances of it in the current spec and in the web platform do have a next() method.

rbuckton commented 5 years ago

@domenic: I feel we are arguing over the wrong thing regardless. What I'm trying to say is that after further reflection I realized the only thing I have issue with in the spec/proposal is the terminology. Where things go doesn't change, its just what they're called that I have a problem with. The problem with the name %IteratorPrototype% is that it doesn't convey the fact that the derived objects are actually both an Iterator and an Iterable.

rbuckton commented 5 years ago

What I'd like to be able to do is leverage these methods in a subclass without needing to be an Iterator, since I could use @@iterator to point to the actual iteration behavior. It is not necessary that Array/Map/Set inherit from this, but having an Iterable global with an %IterablePrototype% with these methods (and having %IteratorPrototype% inherit from %IterablePrototype%) would satisfy my use cases sufficiently and still align with the goals of this proposal.

rbuckton commented 5 years ago

I'm even ok if we have to stick with %IteratorPrototype% as the prototype only and just live with the fact the name feels incorrect, assuming I can still achieve the above (i.e. have this prototype on my object but not have next/return/throw and delegate through @@iterator). If we can agree to have these methods delegate through @@iterator, and name the global Iterable (assuming that is feasible), then I would be fully supportive of this proposal.