tc39 / proposal-deiter

Double-Ended Iterator and Destructuring
MIT License
70 stars 3 forks source link

Transformation and propagation #7

Closed conartist6 closed 2 years ago

conartist6 commented 3 years ago

I've seen several versions of this proposal and I've already written my own, which you can find here.

I want to bring up my concerns again here, which come from my perspective maintaining iter-tools.

My biggest concern is that you cannot reverse an arbitrary iterable performantly. This is because if you write reverseIterate(slice([1, 2, 3], 0, 2)), you've created a problem: the order of operations matters. If you first slice then reverse, the only way to reverse is by doing [...sliced].reverse(). What most people would expect is actually essentially slice(reverseIterate(array), 0, 2). Now you could try to invert the order of operations on the fly, but I think that just makes a bigger mess.

hax commented 2 years ago

@conartist6

First it's not clear what you really proposed, do you propose Array.prototype.reversed() returns a reversed Array ? If that, Array.prototype.toReversed() has already been included in the official proposal: https://github.com/tc39/proposal-change-array-by-copy .

But there is no Set/Map.prototype.toReversed() and I feel it will never be, because it seems no use cases to just reverse the order of Map/Set. An iterator give reverse iteration order might enough.

Or, you may propose it returns an iterator? or iterable? If that, it seems have no much difference with reverse iterator proposal.


reverseIterate(slice([1, 2, 3], 0, 2)) slice(reverseIterate(array), 0, 2)

Order of coz matters. Pipeline or method chaining could make it much clear:

// OOP style
Sequence.of(1, 2, 3).slice(0, 2).toReversed().toArray() // expect [2, 1]
Sequence.of(1, 2, 3).toReversed().slice(0, 2).toArray() // expect [3, 2]

// FP style with F# style pipeline
seqOf(1, 2, 3) |> slice(0, 2) |> reversed |> Array.from // expect [2, 1]
seqOf(1, 2, 3) |> reversed |> slice(0, 2) |> Array.from // expect [3, 2]

Or, maybe you want reversed always means reverse the upstream source, so both return [3, 2]? It's possible, but I never know and use libraries design like that, and it bring the other issues like whether call reversed two times means double the effect so cancel the reversed, or it's idempotent so still reversed.

If you first slice then reverse, the only way to reverse is by doing [...sliced].reverse().

I guess you mean slice(0, 2) (assume it give u a iterable, not array) already have the direction?

slice(0, 2) could be simplified as take(n), and actually it means take the first n elements, so it already one-direction and no way to reverse.

If that's what you mean, yes, some iterator helpers already limit the reversibility.

Or in double-ended proposal, iterators always reversible (means u could always call reverse it), because it just change the end which could be iterated.

There are three type of iterables as double-ended iteration:

  1. double-ended, which could be understand as it support both take(n) and takeLast(n)
  2. front-only, which could be understand as it only support take(n), reverse it will transform it to back-only
  3. back-only, which could be understand as it only support takeLast(n), reverse it will transform it to front-only

Here are the simple result table for common iterators (note, most aggregator methods like toArray, every, etc. are direction-neutral so not listed here) :

EDITED: I moved the table to here: https://github.com/tc39/proposal-deiter/issues/1#issuecomment-1019488928

I think that just makes a bigger mess.

Is it a bigger mess? Maybe, but I think it's the nature of reversing iteration, and it's easy to see the pattern in the table.

conartist6 commented 2 years ago

First it's not clear what you really proposed, do you propose Array.prototype.reversed() returns a reversed Array ?

No, just an generator over the arrays values in reverse order.

But there is no Set/Map.prototype.toReversed() and I feel it will never be, because it seems no use cases to just reverse the order of Map/Set.

Using your own examples anyone who wants to run takeLast or reduceRight on data stored in a Map or Set has a use case. I see no reason this usage should be particularly unlikely.

conartist6 commented 2 years ago

The bulk of my criticism is that the design simply offers nothing of value. If I have to write a completely separate generator implementation based on whether the parameter is provided or not, why should I not just have two completely separate generators?

Why would it be important that I be able to write iter.slice().reverse() when I could just write iter.reverse().slice()?

hax commented 2 years ago

I see no reason this usage should be particularly unlikely.

@conartist6 I mean if Map.p.reverse returns a new Map. Not mean iterator.

hax commented 2 years ago

The bulk of my criticism is that the design simply offers nothing of value. If I have to write a completely separate generator implementation based on whether the parameter is provided or not, why should I not just have two completely separate generators?

Because you don't need to write and maintain two generators if the abstraction have the nature of double-ended. You just need write and maintain one.

And, the motivation of deiter is make [first, ..., last] = seq work, not only for built-ins but also for all userland iterables if they have the nature of double-ended.

Why would it be important that I be able to write iter.slice().reverse() when I could just write iter.reverse().slice()?

Not sure what u mean... I think my previous comment already discuss the importance of the operation order.