Skip to content

behavior of .windows when window size exceeds iterator length #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

Open
bakkot opened this issue Apr 21, 2025 · 7 comments · May be fixed by #14, #15 or #16
Open

behavior of .windows when window size exceeds iterator length #13

bakkot opened this issue Apr 21, 2025 · 7 comments · May be fixed by #14, #15 or #16

Comments

@bakkot
Copy link
Collaborator

bakkot commented Apr 21, 2025

What happens if you do [1, 2].values().windows(3)?

Current spec says this gives you an empty iterator (i.e., no windows at all). As the readme documents, behavior in other languages varies.

My intuition is that it's weird to drop items, but it's also weird to yield a too-small window. I'm not sure what the right behavior is. Throwing is a possibility.

@ljharb
Copy link
Member

ljharb commented Apr 21, 2025

Why is it weird to yield a too-small window as the final result? That's what I'd expect, I think.

@ptomato
Copy link

ptomato commented Apr 21, 2025

I'd expect the return type of .windows(3) to be an iterator yielding zero or more arrays of 3 elements each. Yielding an undersized window means you have to check for undersized windows before you can address elements in the windows, or always remember to deal with the elements being undefined. Programmers will most likely forget this.

@ljharb
Copy link
Member

ljharb commented Apr 21, 2025

Which is more likely, though - a mistake caused by not noticing that your iterator doesn't yield complete chunks, or, one caused by not noticing that a partial chunk was dropped?

This probably calls for an option, if the dropping behavior is needed.

@acutmore
Copy link

I think the default should be that if you ask for a window of 5 items, you need 5 items to get a window.

In Kotlin you have to opt in to "partialWindows": https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.collections/windowed.html

val partialWindows = sequence.take(10).windowed(size = 5, step = 3, partialWindows = true)
println(partialWindows.toList()) // [[1, 2, 3, 4, 5], [4, 5, 6, 7, 8], [7, 8, 9, 10], [10]] 

Having an options bag to choose a mode would align with https://github.com/tc39/proposal-joint-iteration, which currently defaults to "shortest", dropping values that can't be zipped with anything. With windows having an option to choose the padding may also be useful, i.e. choosing 0 instead of undefined.

@ljharb
Copy link
Member

ljharb commented May 21, 2025

After revisiting this in the TG3 call, I realized that my intuition (that smaller groups are ideal/expected) applies to chunks, not to windows.

With "windows", which i find a bit of a confusing name and a bit of an undermotivated use case, I would indeed expect values that don't fit in a window to be dropped, such that you only get windows that match the desired length.

@kriskowal
Copy link
Member

kriskowal commented May 21, 2025

Also following the TG3 call, my expectation is that each window must have the specified number of values. But, I also expect to be able to observe and react to the partial-window that was accumulated if the receiver failed to produce enough elements to fill a window. If there are multiple consumers, I expect only one of those consumers to see the partial-window, if for example they need to dispose of the corresponding resource exactly once, as is consistent with the return value of a generator function.

To that end, please consider using the value of the one-and-only done: true iteration result produced by windows if it fails to populate a single window be the partial-window.

const small = Iterator.from([1, 2, 3, 4, 5]).windows(3);
small.next(); // { done: false, value: [1, 2, 3] }
small.next(); // { done: true, value: [4, 5] }
small.next(); // { done: true, value: undefined }
small.next(); // { done: true, value: undefined }
const empty = Iterator.from([]).windows(3);
empty.next(); // { done: true, value: undefined }
const single = Iterator.from([1, 2, 3]).windows(3);
single.next(); // { done: false, value: [1, 2, 3] }
single.next(); // { done: true, value: undefined }

We already have sufficient affordances, in my opinion, to react to the final/return value of an iterator, such as const partial = yield *iterator.window(3) or driving the iterator protocol directly. But, I regret failing to convince folks that iterator.forEach() should return the value of the done iteration result. I hope that we one day visit a for expression that evaluates to the final value.

const partial = for (const window of sequence().windows(3)) {
  console.log(Math.sum(...window) / 3);
};
if (partial) {
  console.log(Math.sum(...partial) / partial.length);
}

@mhofman
Copy link
Member

mhofman commented May 21, 2025

To that end, please consider using the value of the one-and-only done: true iteration result produced by windows if it fails to populate a single window be the partial-window.

const small = Iterator.from([1, 2, 3, 4, 5]).windows(3);
small.next(); // { done: false, value: [1, 2, 3] }
small.next(); // { done: true, value: [4, 5] }
small.next(); // { done: true, value: undefined }
small.next(); // { done: true, value: undefined }

For reference, this non-undefined value on the first done: true result is consistent with generators returning a value.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment