tc39 / proposal-iterator.range

A proposal for ECMAScript to add a built-in Iterator.range()

Home Page:https://tc39.es/proposal-iterator.range/

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Next step (based on the incubator call of Aug 10 2020)

Jack-Works opened this issue · comments

Reference: https://github.com/tc39/incubator-agendas/blob/master/notes/2020/08-10.md

  1. Semantic decision: Iterable class instead of the current design. The class design also makes it extensible in the future.
  2. Full-featured or minimal design: Add useful methods if possible. Learn from other languages.
  3. API ergonomics:
    1. Class is not easy to use (they need a new op to create).
      1. Possible solution 1: Make it a callable class (like Array vs new Array)
      2. Possible solution 2: A hidden class that not exposed to the global scope (`Range`), only expose the "class builder" as a plain function to new the class in the backend.
    2. I've heard some requirement that if it is a class, the name must be capitalized (`Range` instead of `range`). I didn't like the idea but if we go through 3.i.b this is no longer a problem.
  4. More abstraction on Range?: The idea of a "general" protocol of range that allows future consistent extension of Array Range, String Range, ...etc. Developers can even "implement"/"extends" to make their own ranges. I'm not confident with this idea and doubt if this is really useful or works in JavaScript.
    1. I have no intention to work on this idea currently.
    2. But an important problem to consider: If we have a String.range (or whatever) in the future, should it share a bass class with Number.range and BigInt.range?
  5. Maybe use Cognitive Dimensions of Notation to investigate designs

I have no enough free time to work on this these days so sorry for the delay (it's nearly a whole month since the incubator call).

Generally, my next step is to migrate the current design with no observable API style changes (e.g. from Number.range to new Number.range (and I don't like that)). That means I will choose one of 3.i.a (callable class) or 3.i.b (hidden class, exposed create helper). Please leave comments on which side you'd like to see.

I remain strongly against the class approach, and the notes don't make it clear to me what the persuasive argument was (nor what future extensibility you have in mind, or why that extensibility can't be achieved with the function approach). Per the "base class" question you raise, it feels to me like extensibility is harder with a class hierarchy due to the fragile base class problem.

New classes shouldn't be callable, since class can't be callable. I agree that if it's a constructor, its name should follow the almost universal convention of being in PascalCase (ie, Range, not range).

I'd be very interested to see the results of an informed investigation into various designs.

nor what future extensibility you have in mind, or why that extensibility can't be achieved with the function approach

To extends, means helper methods. Yes, they can be added in the current design but as you can see, it is adding methods on the RangeIteratorPrototype which makes the semantics of helper methods ambiguous. (If we have a .contains(), should it consume the iterator since it is on the iterator prototype?). Another thing to mention: the current design is adding internal slots on a built-in generator.

New classes shouldn't be callable, since class can't be callable.

If so, I'll choose the 3.i.b (hidden class, exposed create helper) route to make the change (at least for now) so I can bypass the PascalCase requirement (also, at least for now, I'd be able to change if it is found harmful or not welcomed design).

A RangeIteratorPrototype is possible with either design, so I'm not clear on why extensibility there has any effect on class vs factory.

When you say "hidden class", if the constructor is reachable, it'd need to have a PascalCase name. If it's not reachable, then it seems like it's just the factory approach, where range is a function that produces an iterator (that inherits from RangeIteratorPrototype)?

A RangeIteratorPrototype is possible with either design

Yes, I have mentioned that "which makes the semantics of helper methods ambiguous. (If we have a .contains(), should it consume the iterator since it is on the iterator prototype?)."

When you say "hidden class", if the constructor is reachable, it'd need to have a PascalCase name. If it's not reachable, then it seems like it's just the factory approach

It's not the case. I'm meaning (the second approach):

const r = Number.range(0, 1)
const hiddenRangeCtor = r.constructor
assert(hiddenRangeCtor.name === 'NumberRange') // or other name
assert(r instanceof hiddenRangeCtor)
assert(!(r instanceof Number.range))

Ah, I see what you mean. In either case, yes, I would assume a generic .contains() consumes the iterator, but that a Range-specific contains need not.

Thanks, your code sample matches my expectation if we were to go with the class instance approach. Presumably in that case we'd make hiddenRangeCtor also non-constructible (or not accessible via .constructor), so you couldn't do new hiddenRangeCtor?

Presumably in that case we'd make hiddenRangeCtor also non-constructible (or not accessible via .constructor), so you couldn't do new hiddenRangeCtor?

I think it is no harm to get the underlying Range class and construct it manually. My expectation is that: Number.range = (...args) => new #hiddenNumberRangeCtor#(...args) to improve ergonomics of the API.

@codehag hi! Could you give help to use Cognitive Dimensions of Notation to investigate which semantics is better? Current semantics VS #42. It seems like both Iterator and Iterables have their supporters and it's hard to going on now.

commented

cc @Felienne

This looks interesting. I want to think about it a bit, but wanted to make sure Felienne also saw.

In the MDN Iterator example the authors aptly named the function makeRangeIterator as to highlight that the result of that operation would be an iterator and not a data structure (Iterable).

From my experience with other libraries: Number.range(0, 100, 2) suggests to return a data structure (lodash). To suggest an Iterator I would be looking at something like Iterator.numberRange(0, 100, 2) or shorter Iter.range(0, 100, 2).

While I think that having an Iterator is a perfectly fine simple step, I think there can be an argument made for a data structure: doing an .includes when having a data structure allows for a O(1) complexity (sample implementation) while doing an .includes on an Iterable would be a O(N) complexity.

Adding both Iterator.range and Number.range seems not necessary. I'm wondering if we choose the name as Iterator.range, does it release the harm in of not-reusbility?

Iterator.range kind of kills the ability to support multiple number types - ie, BigInt, or a future Decimal.

One would need to get creative about naming: Iterator.bigIntRange but there might be another name that is better suited?

Aside from this, I still think that there are enough arguments for having an Iterable over a Iterator.

@martinheidegger i don't agree with that last point; i still haven't heard any arguments that hold. There's lots of discussion about that on #42, though, so let's have that there.