-
Notifications
You must be signed in to change notification settings - Fork 34
Should take() close the iterator when remaining is 0? #71
Comments
you can still use the iterator afterwards though. |
I agree it should be closed. @senocular gave the most common use case for If people do want to keep using the iterator, they should need to specify explicitly that they don't want to close it already:
So this |
We are actually running into the reverse of this in nodejs/node#46980 and it seems like in practice the expectation is for the iterator not to be closed to enable cases like:
|
Hmm. I think a "get the next I see where the confusion there is coming from, though. Honestly |
A second parameter as discussed above does seem reasonable though. |
|
const iter = (function * (i) { yield ++i })(0);
const iter1 = iter.take(4);
const iter2 = iter.take(4);
iter2.next().value; // do you expect 1 or 5? |
I think 1 is the more correct behavior |
Is this is the most common use case? It is definitely very common, but wanting to do something with the first N items and something else with the rest like @benjamingr is suggesting is also something people do a lot. It’s certainly a pattern I have needed many times.
Yeah I think I agree. @zloirock’s example highlights how you can easily get into problems using (That said, having Anyway! A function that returns two iterators (one for the first N items and another for the all the items after) might be more appropriate for that use case. For example: const [first4, tail] = iter.splitAt(4);
first4.next().value === 1
tail.next().value === 5 (Other names for similar operations in other languages/tools include Or maybe a second argument to iter
.drop(4, (first4) => {
first4.next().value === 1
})
.map((item) => {
// item = 5, then 6, then 7, ...
}); Either of those gives people a suggested solution for needing to do one thing with the first N and something else with the rest, leaving |
What do you guys think of having an option to |
You can have a "eagerly get me the first N items" method (i.e. just call
It doesn't, really - It's just that only JS really has a notion of automatically closing an iterator when you stop iterating it before it ends, so only JS has to answer the question of whether the underlying thing gets closed when you exhaust the result of |
I meant with regards to this issue (whether the "input iterator" is closed or not). Though in Rust (if I remember correctly), I suspect in most other languages the bigger difference is that there are usually iterable helpers and not iterator helpers which also (somewhat) subverts the issue. |
Ok, then a different name (though I thought splitAt would mean "split the values at" and not "give me two iterators")? |
Node would still have to close streams (even if it knows they're usable) for safety since users expect |
Ah, sure. In that case it's the "get the next That is, something like Iterator.prototype.getN = function (N) {
let out = [];
for (let i = 0; i < N; ++i) {
let { done, value } = this.next();
if (done) break;
out.push(value);
}
return out;
};
// usage
let iter = new Set([0, 1, 2, 3, 4, 5, 6]).values();
let first3 = iter.getN(3); // first3 is [0, 1, 2]
let next3 = iter.getN(3); // next3 is [3, 4, 5]
iter.next(); // gives { done: false, value: 6 }
iter.next(); // gives { done: true } If that's not the API you're thinking of, can you write it out in more detail? |
For sure! I guess I assumed a reasonable I guess I’m wanting a way to address the use case without being eager, like your |
If you start iterating the second thing it's forced to immediately fetch and buffer all the results from the first thing, so it's still pretty eager. That's unavoidable given the iterator API, unfortunately. I think being explicit about that (by returning an array) is probably clearer. (I guess in principle you could make it such that attempting to advance the second half without having exhausted the first would throw, but that seems awkward to use.) |
Sure. It gives you more options, though (e.g. only buffering as much as it has to), and you can continue to chain iterator helper methods off it, which lets you do more in a single, non-eager pass. It’s better than the worst case.
That’s fair! I can buy that argument. 🙂
Yeah, I think that would entirely defeat the value of it, TBH. What I want here (even if I’m not going to get it) is something where I can safely iterate over the sections I want to treat differently, and not have to worry whether my program will crash or give the wrong results if I screw up the timing of things (easy to happen if consuming the iterator involves async operations somewhere). |
Right - and to be explicit the ask here is to rename By the way, who I can talk to to make sure this gets visibility? I think "visisibility" is a good first step before I approach my delegates through Node/Microsoft and the relevant orgs (e.g. TSC in the Node case) and I'd rather just fix this instead of blocking or delaying the proposal which I am very much eager/happy for. |
@michaelficarra (who is my coworker) is the person you want, as the most active champion. Normally I could ping him at work but I'm theoretically on vacation (whoops). I'll send a message on Matrix at least. |
Awesome, I'll do my best to hold off any changes in Node.js's implementation until we can resolve this through the spec process. |
I think that And the I think |
I kind of agree; “take” to me doesn’t destroy the cookie jar, it just takes some cookies out of it. I’d expect “truncate” to imply closing the iterator, for example. |
How so? You're... getting stuff from it. Getting things from an iterator is equivalent to advancing it.
|
To put it another way: iterator helpers are generally set up on the assumption that you're not going to re-use the receiver. That's because the receiver draws from the same source as the result of the helper, which means that using both of them will result in seeing skipped items. So I don't think we should have any methods which encourage using both the result of the helper and also still using the original iterator (at least not without you having to carefully opt in) - i.e., we should not have a non-closing |
That is also my intuition.
I still don't think that's the common case in other languages, usages of chaining I think it's best to be cautious and avoid ambiguous naming - the data point we do have right now is that with the current proposal users found the behavior confusing and unintuitive.
I'm not sure I agree given people are asking for this. I would be content with naming the existing one in a way that is less ambiguous like |
And to be explicit I agree with @bakkot that having a non-closing |
This comment was marked as duplicate.
This comment was marked as duplicate.
This comment was marked as outdated.
This comment was marked as outdated.
Sorry, I didn't mean to claim that. There's not a closing I just think any method ought to close (by default), because the normal way of using these things is that you don't use the receiver again after chaining a helper off of it. (Note that With that said, I am OK with having an option to give you non-closing (as in Maybe it's still fine? |
FWIW, this seems more like a chunk. The race could be fixed, too, by preventing the next outer const iter = getIterator().chunk(4);
for (const chunk of iter) {
for (const it of chunk) {
}
} |
Name bike shed if we are leaving it closed: (Edit: I also like |
I should say that I suggested |
IMO |
take() returns undefined when remaining is 0 without calling back up to close the iterator. I'd expect iterator to get closed when this happens. For example
The text was updated successfully, but these errors were encountered: