Array.prototype.reversed() (+Map, Set)

I'm offering this as a competitor to existing proposals for bidirectional or reverse iteration, namely Bidirectional iterators and

The idea is to create Array.prototype.reversed, as well as Map.prototype.reversed and Set.prototype.reversed methods.

Usage would look like this:

for (const value of values.reversed()) {

What I see as the advantages:

  • Very easy to specify and implement.
  • It is clear that once iteration is started it can proceed in only one direction.
  • All existing tooling for working with iterators still functions as intended. There's nothing new it needs to know about.
  • There are no runtime errors to worry about. With a system that describes a generic approach to reverse iteration a new common error will be "Reverse iteration is not implemented".
  • With no (new) runtime errors it will be possible with existing static analysis tools to verify that code will not throw an error. If a reversed() method is not present: a type system warns you. If a reversed() method is present but its return value is not iterable: a type system warns you.
  • Speaking of type systems, there is no base class for Iterators. For typescript this means that when you write Iterable<T> you're just declaring that an iterator's next() method should return an object of type { done: boolean, value: T }. Adding a previous() method complicates the type checking. What if for some reason the types returned by the previous() and next() methods conflict? It shouldn't happen, but it probably would and the resultant error would be highly cryptic.
  • Finally it offers myriad syntactic options. Obviously it would be best to choose a single convention to follow, but all of the following would be possible: myMap.keys().reversed(), myMap.keys.reversed() or just myMap.keysReversed() method. Authors of libraries would be free to chose the most sensible syntax for their use cases.

Also by avoiding creating additional mechanisms it ensures that additional work is not needed to support reverse iterator of async iterables. You would simply write reversed: () => ({ [Symbol.iterator](): { ... }, [Symbol.asyncIterator](): { ... } }).

If my other proposal for Symbol.syncAndAsyncIterator were to be accepted those too would work without additional effort.

I like the idea, but it doesn't make sense to include it for maps and sets.

Better would be just tacking it onto the iterable protocol as an optional member:

1 Like

The iterable protocol consists of a single symbol, Symbol.iterator (returning an iterator). I don't see that it's possible to add anything to the iterable protocol. You could add it to the iterator protocol of course, but the iterator protocol in my mind is an implementation detail which the vast majority of the time should be hidden away behind higher level constructs.

My bigger concern really is that the algorithm for taking an arbitrary IterableIterator and reversing it is already perfectly well supported in javascript, because it is [...iter].reverse(). As soon as you do any operation on the iterable, such as slicing it, it is no longer possible to reverse without that array allocation. This is because reversing a slice of an array is not the same operation as slicing the reverse of an array. So if reversing is only really possible as an operation on allocated objects (like arrays), then why would you define it as an operation on iterators?

This is very much not how Immutable's reversed lists as referenced in that issue are implemented. They elide the reversal entirely. And iterables can themselves be iterators, as is the case with generator instances.

So no, those concerns do not hold with my suggestion there.

1 Like

I don't see how this is so different from Lee Bryon's proposal. Yes, you're creating a reverse() method on each iterator kind instead of shared reverse() method that goes to call a symbol-keyed method, but the end result is exactly the same.

The first would require a reversed method on the ArrayIteratorPrototype/MapIteratorPrototype/etc objects, which you seemed to reject initially? The second is not actually possible. The third would mean having to create three methods reversedKeys()/reversedValues()/reversedEntries() on all collection prototypes, which is quite some overhead.

I would suggest instead of considering this a "competitor" to the existing proposal, you open an issue there and propose a simplification by removing "The ReverseIterable Interface" and having simple reverse() methods on iterators instead of implementing a symbol protocol that's not used by any syntax.

Yeah I really don't like keys().reversed() for all the reasons I just laid out, and we can rule out anything that's not possible. I guess I think the benefits makes it worth some cost.

As for Lee's proposal, I consider it dead. In particular it is based on immutable.js, which is itself seemingly dead to Lee. I spent two years trying to poke and prod him into giving the project some of the attention it needs, but recently I forked it and members of the community have taken over support and new development. I will not tie any more of my prospects to him. He's within his rights to shift his focus to new projects and his job and his life, and I think I'm within my rights to compete with him instead of trying to follow him around everywhere.

Sorry @claudiameadows, I misunderstood.

CC @hax @Kingwl