tc39 / proposal-array-last

A JavaScript TC39 Proposal for getting the last element from an array
311 stars 13 forks source link

Using iterable rest syntax #31

Open jridgewell opened 4 years ago

jridgewell commented 4 years ago

I recently had the need to get the last-ish elements again. Twice actually:

  1. Getting matchIndex from String.p.replace when 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.
    });
  2. 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.

keithamus commented 4 years ago

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).

ljharb commented 4 years ago

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)

keithamus commented 4 years ago

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?

ljharb commented 4 years ago

Yes, exactly that :-)

jridgewell commented 4 years ago

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.

hax commented 4 years ago

I like the idea, python support similar feature. But there are some problems:

  1. 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]()?

  2. What happened on [...rest, secondToLast, last] = [1]? I suppose the answer would be rest=[], secondToLast=1, last=undefined, so conceptually last is not last, but secondToLast in this case 😂 . Note, we could make it rest=[], 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.

hax commented 4 years ago

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.

hax commented 4 years ago

Have an idea and create a gist for it: https://gist.github.com/hax/285172c95550d3a46c4c997a13ce3614