Using iterable rest syntax
I recently had the need to get the last-ish elements again. Twice actually:
-
Getting
matchIndexfromString.p.replacewhen using a function:string.replace(pattern, (fullMatch, ...submatches, matchIndex, fullString) => { // `matchIndex` is always the second to last param (the full string is the last param). // There may be many submatch params, depending on the pattern. }); -
Getting the last element of an array of
IntersectionObserverEntry:const io = new IntersectionObserver(entries => { // There may be many entries, but only the last one // is needed to see if element is currently visible. // The rest are stale. const [..., last] = entries; })
I propose repurposing iterable's rest ... syntax (with an optional binding) to get the last (or last-ish) elements:
// Grab the last, don't care about anything else.
const [..., last] = iterable;
// Grab second-to-last, throw the rest away.
const [..., secondToLast, last] = iterable;
// Can even keep the initial elements
const [...initial, last] = iterable;
// Can repurpose inside params:
function foo(..., last) {
}
It's not perfect (there's no equivalent way to set the last item), but it seems natural with destructuring syntax. We just need to prevent multiple ... from being used it the same iterable destructure.
I really like this, it can provide a great deal of flexibility in getting first/last elements, I could imagime it being used like [first, second, ..., last] = iterable. My only concern is that it'd have to consume the entire iterator, unless we added new protocols to more efficiently get elements at arbitrary indexes (which is antithetical to Iterator's design).
if this worked with just the three dots, I’d also expect to be able to put a binding there and collect the third through penultimate items.
Also, I’d expect [first, ...] to exhaust the iterator (where [first] does not exhaust it)
I’d also expect to be able to put a binding there and collect the third through penultimate items.
I assume you mean converting it to [first, second, ...some, last], right?
Yes, exactly that :-)
My only concern is that it'd have to consume the entire iterator, unless we added new protocols to more efficiently get elements at arbitrary indexes (which is antithetical to Iterator's design).
I think they can optimize this for arrays that have not been patched. For anything else, yes, I think they'll have to consume the iterator.
I like the idea, python support similar feature. But there are some problems:
-
Performance. It seems these syntax need to exhaust the iterator, so it would be bad for potential long list when you could use O(1) access time. And how to handle infinite iterable, is it just make your browser halted? Could we have some way to throw preventatively for these cases? For example,
iterator[Symbol.disallowReverse]()? -
What happened on
[...rest, secondToLast, last] = [1]? I suppose the answer would berest=[], secondToLast=1, last=undefined, so conceptually last is notlast, butsecondToLastin this case 😂 . Note, we could make itrest=[], secondToLast=undefined, last=1, but we need also explain how[a, b, ...c, d, e] = [1, 2, 3]behave. I remember vaguely there are some weird cases in other language which have similar feature, but can't find the discussion now.
Maybe [...rest, secondToLast, last] = o could be sugar to [last, secondToLast, ...rest] = o[Symbol.reverse]() ? See https://gist.github.com/leobalter/092fc36adccfcc86e8e7b074817078e1 for stage 1 reverse iterator proposal.
Have an idea and create a gist for it: https://gist.github.com/hax/285172c95550d3a46c4c997a13ce3614