-
Notifications
You must be signed in to change notification settings - Fork 13
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make Number.range() return re-usable value #17
Comments
This is sort of what I assumed the interface would be as well: |
So |
We decided against returning an iterator from Intl.Segmenter.prototype.segment() after long discussions about ergonomics (see, e.g., tc39/proposal-intl-segmenter#93, and other issues in that proposal). Basically, if the return value of I think we should explore other options here. For example, if you write |
Iterator.from(...) and [Symbol.iterator] is basically the same in this case, they are both not ergonomics for chaining iterator helpers on it. |
Yes, this is a thing that's been under discussion for iterator helpers in general. It's also important to be able to use them for Arrays, which are iterable but not iterators. I liked this story: You can use |
Iterators are already iterable, so the only discussion point seems to be re-usability. I'd argue if you want to reuse it you should call it again, it's more explicit and its weird to me that you'd need to make two calls ( |
My intuition around the iterator protocol was that we're sort of generalizing part of the functionality Arrays. Arrays support @Jack-Works Patching it up with @devsnek What in particular is weird about this? If you have a |
It's the way generators already work - the generator is called (perhaps with arguments) to produce the (iterable) iterator. The generator itself is not iterable, you have to call it every time you want to iterate. |
@ljharb I agree with this description of what generators are, but I don't see how that applies here and why |
The correct pattern here is definitely to return an iterator. If we want iterables for some reason the pattern you're looking for is a If it helps: |
Another way to think of it: JS moved reusability out of the "Iterator" protocol itself ( @littledan I'm admittedly not very familiar with the Intl.Segmenter proposal but a cursory glance seems to be this, which holds to the pattern but is kind of weird: It's worth noting that Intl has always had different patterns compared to the rest of the stdlib, most notably in their constructor patterns ( |
Well, I think Intl constructors are a good design that could make sense among anything TC39 creates, but we're getting a bit off-topic. For new APIs, I'd like to see if we can use common design patterns that make sense in general, and having Intl.Segmenter follow the iteration protocol (unlike Intl.v8BreakIterator) was intended to be part of that. |
my point was basically that both Number.range() and Intl.Segmenter().segement follow the pattern as far as i can tell, but i wouldn't really equate one directly to the other, because they have to (and do) represent different things. On topic again, |
Another thought: (working but it's a crazy design) Add a const range = Number.range(0, 5);
for (let i of range) { } // use range[@@iterator] which return a cloned range
for (let i of range) { } // use range[@@iterator] which return a cloned range
// range is not consumed yet.
range.take(...).toArray() // now it's consumed |
iterators already have for (let i of range()) { }
for (let i of range()) { } seem like a much more obvious way of saying you're using a separate range for each one? I don't understand why you want to make that implicit. |
An Intl.Segmenter Segments instance, like an array/set/map/string/etc., is Iterable but not itself an Iterator—it is primarily a factory for constructing iterators, and also supports a So the question here is whether |
Return an iteratorCurrent design.
Number.range(...): RangeIterator<number> Behave like
|
I think I've objected to that idea a few times over at this point... |
I think it's a question we should discuss at plenary, since not everyone is in agreement on this thread. |
We discussed the issue in plenary, but I don't think we reached a particular conclusion. What are the next steps? |
I'm going to study the ranges in other languages to make a comparison then decide which pattern is better 👀👀 |
Interestingly, I found this re-usable problem in Rust. Rust is using the fn main() {
let mut a = std::ops::Range { start: 3, end: 5 };
print!("Used: {:?}\t", a.next());
for i in a {
print!("In loop: {:?}\t", i)
};
}
But I think manually call the |
In my recent research of But before switching to the Iterable semantics, there're a few problems that we need to resolve. Use with Iterator Helper// Now
Number.range(0, 10).take(5).toArray()
// After
Iterator.from(Number.range(0, 10)).take(5).toArray() I have an idea at tc39/proposal-iterator-helpers#78 (comment) but I'm not sure about it. The naming problem
Let it to be a class// before
Number.range(0, 1)
// after
new Number.Range(0, 1)
// or Callable class
Number.Range(0, 1) I have no idea if adding a new Let it to be a helper to create the Range classNumber.range(...) // implicitly calls new Range(...) Another possible route: merge into Iterator namespace, becomes
|
Can you elaborate on why you think it would be better to add an entire class for something that seems only likely to be used to generate a single iterator? |
I'm not sure why that would be necessary; To be clear, I am still convinced that unless this returns an iterator directly, it should not be added to the language. |
Add two new slides https://docs.google.com/presentation/d/116FDDK2klJoEL8s2Q7UXiDApC681N-Q9SwpC0toAzTU/edit#slide=id.g8c83f0abc4_2_5 and https://docs.google.com/presentation/d/116FDDK2klJoEL8s2Q7UXiDApC681N-Q9SwpC0toAzTU/edit#slide=id.g8c83f0abc4_2_27. If I missed something please tell me before the presentation so I can add them to provide a more complete view |
@ljharb As the https://github.com/tc39/proposal-Number.range/blob/master/compare.md#return-type , most other programming languages (we can investigate more languages if needed) do not returns an one-time consumed iterator directly.
I think @Jack-Works means writing |
While JS should always be informed by other languages, it is absolutely not constrained by them - new JS features should follow JS idioms, and values/keys/entries/matchAll very much define that idiom. Again, I don't understand why |
Yes. I'd like to do it so. If we decided the re-usable problem is important, either we need IteratorPrototype.map = function* (f) {
const iter = Iterator.from(this)
// ...
} Then, set %RangeIteratorPrototype%.[[Prototype]] to %IteratorPrototype%, we can have both re-usable semantics and |
I would assume Like every builtin iterator, it would also have a |
Iterators/Generators and related Ranges are very common feature in many languages, especially JS iterators/generators is inspired by python design. The core concept are very same: Iterables are objects which can be iterated, iterators are special object which delegate the iteration behavior for iterables, generators are syntax which help implement iterators in user land. So the common cases are a method returns an object, and it happened to have implemented iterable protocol. Actually return iterators directly are special cases. If we see
In the context of this issue, these important natures make
So it's more close to We should also notice I think @sffc have given a very reasonable comment:
And I believe |
If you think range should be an iterable please specify it as a constructor to stay consistent with our language patterns. |
@devsnek Do u mean it should be |
As per the chart I posted above it needs to be an iterable class or a function that returns an iterator. I would not be comfortable with anything else. |
I'm not sure, is this only apply to iterable? Why only iterables need to be constructor and can't created by a static factory method? What if we have both And again, let's first decide whether it should be iterable (semantic issue) and then consider surface api problems. |
I think the reason I feel uncomfortable with Number.range = function ( start, end, step ) {
return new Number.Interval( start, end ).values( step ); // an iterator over the interval
}; Even without a first-class Perhaps Note (2022-07-07): #57 makes a similar suggestion to split the proposal into two proposals. |
So. When we started working on iterator helpers, it was not clear whether they would be iterator helpers or iterable helpers, and the main disagreement was around how reusability is represented in iterators and iterables. In particular people argued, like you do, that using iterators instead of iterables meant you lost reusability. The conclusion we came to however, was that in JS the primitive of reuse was not actually the iterables, but function calls. This is mainly due to the fact that "iterable" does not describe a non-scalar type that can produce an iterator, but rather any object that happens to have a Symbol.iterator property, including iterators themselves. The signs of reusability of iterators were actually one of the following: The base being an instance of some non-scalar type (like Array.prototype[Symbol.iterator]), or getting the iterator directly from an explicit function call. I want to stress that this is not just my sole opinion of how you should use iterables, but the result of research over many months by many people to understand what the best way to deal with iterators in JS should be, and it allowed us to gain consensus and move forward with that proposal. Number is a scalar type, and Finally, my opinion on this specific choice: I think you should choose
Intl is somewhat different because it uses the factory pattern for all its top level apis, unlike the rest of our stdlib. I don't agree with the design but I don't think it would be constructive to discuss changing it at this point. |
@devsnek Is there any link of details about "research over many months by many people to understand what the best way to deal with iterators in JS should be" so I can learn it?? And I want to point out, we are not (only) talking about reuse iterator, but by itself, range (or interval), could be a "thing" beyond iteration. |
old issues about iterables in the proposal repo i guess? might be some stuff in the tc39 irc logs too.
That's totally valid, it would just be a |
Another possible real-world usage that requires reusable value: facebook/react#20707 |
And again, that's a general iterator issue, and wouldn't be affected by anything we do with Range in any case. |
I believe this issue has been resolved by renaming it to |
The problems with polymorphic functions are :
|
Polyfillability doesn’t impact proposals. |
The motivation and the main use cases of the proposal is about simple iteration of integers (like 1 to 100), change name to On the other side, when we went to |
An allternative may be to provide room for a callback for each yielded value
Renaming it to |
|
@ogbotemi-2000 Yes, python (and most programming languages) range api return re-useable iterables not one-shot iterators. Personally I would like the API follow python. Unfortunately it's very controversial in the committee. Renaming is the best and tradeoff I can tell, or we will never advance. |
I would consider separating the iterator from the object returned from
Number.range()
, such thatNumber.range()
returns an immutable object (with getter properties like.from
,.to
, etc), and then calling[Symbol.iterator]
returns a "fresh" iterator with a.next()
method. You can re-use the ranges. In other words, we should consider making the following code work:This is what we are doing in the Intl.Segmenter proposal.
https://github.com/tc39/proposal-intl-segmenter/issues
CC @gibson042
The text was updated successfully, but these errors were encountered: