Attendees:
- Jack-Works (JWK)
- Hax (JHX)
- Shu-yu Guo (SYG)
- Michael Ficarra (MF)
- Richard Gibson (RGN)
- Jordan Harband (JHD)
JHX: (Recap of the proposal: https://github.com/tc39/proposal-deiter/blob/main/why-deiter.md)
JHX: First question: is double-ended iterator needed as the underlying mechanism for double-ended destructuring? First, index-based approach doesn't work for JS because destructuring in JS is already based on iterables. Rust has double-ended destructuring but only Array is supported right now.
There are two ways to get DE destructuring without DE iterators. First: array-based. Get ...rest, and then do .splice on it. Seems very simple but has a scaling problem when the list is very long. Common use case is to only take the last few elements. I believe engines can optimize builtin types, but would only apply to builtin iterators, userland iterators would still suffer.
MM said last meeting destructuring is not used where scaling matters. But is it true? Let [first] = iterable
doesn't have scale issue now but let [..., last] = iterable
will have scale issue. This mechanism in my opinion won't work as expected because engineers have issue anticipating performance requirements.
The second mechanism is what this proposal proposes: DE iterator. Seems complex but actual similar complexity to the Array-based mechanism.
MF: How does one use this the DE iterator protocol whether the thing they have provides regular iteration or double-ended iteration without trying to iterate?
JHX: I have a specific issue in the issue list for how to mark iterators as DE. I think this is a separate issue to cover later.
MF: Let's cover it later.
JHX: DE iterator generalizes the protocol at the language level so library authors can implement the protocol. If DE iterator protocol isn't implemented, using DE destructuring will throw TypeError, ensuring deterministic performance expectation.
Compared to the reverse iterator protocol, DE iterator covers all use cases of reverse iterator and is a more general mechanism. For the total cost of two proposals, cost of DE iterators may be smaller.
For summary see https://github.com/tc39/proposal-deiter/blob/main/why-deiter.md
JHD: Question. If I do [first, ...rest]
, I get the exact same values guaranteed if I explicitly iterate out rest.length - 1
times. The rest syntax here is consumer-side sugar. With a special protocol for DE iteration, although the builtins wouldn't do this, a user object could make it so that [first, ...rest, last]
give you different results from [first, ...rest]
popping off the last value. Is that a concern?
MF: This is a property we typically call a "law". It's not enforced in any way, the invariant you're expecting is documented and any conforming implementations are expected to conform to the invariant. There's no efficient way to check if the law holds, not possible in JS. These are typically just agreements. So someone using a protocol that has any law is opening themselves up to risk that the implementers have violated the law. That shouldn't dissuade us from having protocols that have laws.
JHD: I'm not talking about the general case. There are cases where laws are enforced, like Proxy returns.
MF: How are these checked?
JHD: Like object returns.
MF: Those are easily tested.
JHD: Oh, yes, not arguing that the law expectations should trump the performance expectations. I'm just asking that because it's a variant of an existing thing. The existing thing doesn't have this invariant and the new thing does, while the two are very close looking in code and in mental model.
MF: It's not a case though where any existing code has to be reconsidered.
JHD: It's the refactoring hazard specifically.
JHX: If I understand question correctly, I feel reverse iterator has similar issue.
JHD: Reverse iterator also has this issue.
JWK: I think it's implementers' responsibility to ensure the law holds.
JHX: Just like normal iteration, yeah, like you can override the built-in iterator.
MF: Jordan is pointing out that there's a relationship between regular iteration and DE iteration.
JHD: Ideally it'd be impossible to write such an poorly performed iterator.
MF: Just not possible in general to detect due to infinite iteration.
JHD: I don't care about infinite iteration for this case. I'm concerned the ability to implement an iterator that behaves differently forwards and backwards.
JHX: I think DE iterator is better for this than reverse iterator. Reverse iterator is a different iterator, and DE iterator is just one iterator so you just write it once.
JHD: I'm talking about someone intentionally writing a misbehaved iterator. Right now, an iterator can't detect if I'm using rest syntax and give me different results.
MF: But we're not detecting rest syntax, we're detecting if there's use of the DE protocol. This is inconsistent with how we make decisions though, just because we can't enforce something shouldn't mean we don't consider the proposal. It's good to bring up the relation because there's a lot to talk about in that space. Is that a problem with the design in that there are two ways to do the same thing? If you implement DE, did you implement regular iterator for free? How do we want that implication be realized?
SYG: What about the original question?
JHD: I think Hax has written up a good analysis. There are two ways: mechanism A: consume and create an array, or mechanism B: a new protocol. I have bikesheddy thoughts about the exact semantics about mechanism B.
MF: I'm wholly in support of mechanism B and it's a good way to go, we just have open questions to iron out.
JHX: My question: do you think mechanism A is okay for DE destructuring?
MF: I think we need to consider infinite iterators as equal citizens to finite iterators.
SYG, JHD: How does destructuring work with infinite iterators?
JHX: People often need the last thing.
JHD: What does it mean for infinite iterators?
MF: You can combine two infinite iterators like all halves between 0 and 100.
JHX: Very simple example is repeat.
JHD: If you're talking about laws, Michael, what you just described is technically correct but nonsensical.
SYG: I don't think infinite iterators should be considered an equal citizen, since the uncontroversial use case we're solving is not iterators but DE destructuring.
JWK: Actually you can't distinguish if an iterator is infinite in the language level anyway. The goal is to consider built-in and userland iterators equal citizens.
SYG: That said I still like mechanism B. Part of the reasoning is that we already made a choice about the performance tradeoff between something like using array indices and having a protocol: destructuring already works with an iterable protocol. I'm lukewarm on adding a new protocol for the DE destructuring case, where scaling probably doesn't matter most of the time, but using the existing forward iterator protocol seems like an abuse of that protocol.
JHD: Let me provide a data point. In Airbnb and Coinbase's codebase, if destructuring worked on array indices instead of iterables, there would be zero differences between them. Iterable protocol hasn't even paid for itself IMO.
JWK: In our codebase we use async generators.
JHD: Reason I bring up anecdata here because I actually agree with MM. I don't think performance matters here.
(Discussion of anecdata)
JHX: Summary: people on this call like mechanism B but we need to make sure it works fine for the small cases instead of theoretical cases.
SYG: I advise to not lose sight of the uncontroversial use case that folks want to solve: DE destructuring.
JWK: I think no matter what we choose the most general use case (deconstructing on the last of the array) won't be affected.
An unrelated question: that destructuring auto-closes iterators is weird.