Array tail destructuring

# Cyril Auburtin (8 years ago)

It was possibly already discussed, but why isn't:

var [...a, last] = [1,2,3];

supported?

I don't see the problem, as long as there's one ... only

# Michael Theriot (8 years ago)

I think this is because there's no universal way of determining when an iterator ends (beforehand). The only way this could work for all iterators would require popping values off of a after they've been added.

# Dmitry Soshnikov (8 years ago)

Yeah, because it's not a pattern patching, and access to array elements may have side effects, it's seems hard to specify/implement. Otherwise, destructuring might analyze the pattern, see the last element is required, extract it, and then do iteration for others.

Dmitry

# Cyril Auburtin (8 years ago)

ah ok, I forgot about other types of iterables, like infinite ones:

function* ones(){ while(true) yield 1; }
var [...a]=ones(); // freezes

2016-10-01 18:24 GMT+02:00 Dmitry Soshnikov <dmitry.soshnikov at gmail.com>:

# Olivier Lalonde (8 years ago)
function* ones(){ while(true) yield 1; }
var [...a]=ones(); // freezes

So if that freezes, whats the problem with [...a, last]? It would just freeze as well...

access to array elements may have side effects

Doesn't [a, b,...vals] access array elements?

# Jordan Harband (8 years ago)

No, it calls [Symbol.iterator]() on the RHS iterable, using the iterator protocol. It has nothing to do with arrays.

# Olivier Lalonde (8 years ago)

So what's the problem with [...a, last] that [...a] doesn't have? I still don't get it.

# Caitlin Potter (8 years ago)

On Oct 2, 2016, at 9:30 AM, Olivier Lalonde <olalonde at gmail.com> wrote:

So what's the problem with [...a, last] that [...a] doesn't have? I still don't get it.

Since you don’t know when the iterator produced for …a will terminate, there’s no way to know when you need to stop iterating …a and move onto the next item last.

A possible workaround would be to track the number of values iterated, and compare this index with the RHS.length/size - 2, but this would impose a maximum of one non-final rest element in the array, and require that the RHS necessarily has a known length/size (which would probably end up being a new property with a name like Symbol.length or something, added to all collection types as an alias for .size() or .length()).

Another option would be to make knowledge of the “maximum” iterated length be known at parse-time (something like […a{10}, last], where …a’s iterator would stop after at most 10 elements. This wouldn’t require a new @@length symbol, and would allow multiple non-final rest elements, but it’s not really very useful in practice.

# Awal Garg (8 years ago)

On Oct 2, 2016, at 9:30 AM, Olivier Lalonde <olalonde at gmail.com> wrote:

So what's the problem with [...a, last] that [...a] doesn't have? I still don't get it.

Since you don’t know when the iterator produced for …a will terminate,

there’s no way to know when you need to stop iterating …a and move onto the next item last.

I think Olivier's point is that there is no way to know when you should stop iterating in the case of [...a] either - hence the two cases are equivalently problematic, if at all. Pulling the last element out when the iteration stops is a different concern IMO which seems trivial to solve:

function destructureTail(it) {
    let head = [], tail;
    tail = it.next().value;
    for (let o of it) {
        head.push(tail);
        tail = o;
    }
    return { head, tail };
}

function* range(from, to) { while(from < to) yield from++; }

destructureTail(range(0, 0)); // empty iterator: head is an empty array and
tail is undefined
destructureTail(range(0, 1)); // only one element: head is an empty array
and tail is the single element
destructureTail(range(0, 2));
destructureTail(range(0, 3));
// destructureTail(range(0, Infinity)); // just hangs, like [...range(0,
Infinity)] would hang, which makes sense
# Caitlin Potter (8 years ago)

On Oct 2, 2016, at 10:50 AM, Awal Garg <awalgarg at gmail.com> wrote:

On Oct 2, 2016, at 9:30 AM, Olivier Lalonde <olalonde at gmail.com> wrote:

So what's the problem with [...a, last] that [...a] doesn't have? I still don't get it.

Since you don’t know when the iterator produced for …a will terminate, there’s no way to know when you need to stop iterating …a and move onto the next item last.

That statement is factually incorrect. There is a simple criteria to know when to terminate the iteration for a final rest element, which is when the iterator returns a result object with "done": true.

There is no condition to determine when to switch from a non-final rest element to some other element. That is a problem which needs to be addressed.

# Tab Atkins Jr. (8 years ago)

On Sun, Oct 2, 2016 at 2:11 AM, Caitlin Potter <caitpotter88 at gmail.com> wrote:

On Oct 2, 2016, at 10:50 AM, Awal Garg <awalgarg at gmail.com> wrote:

On Oct 2, 2016, at 9:30 AM, Olivier Lalonde <olalonde at gmail.com> wrote:

So what's the problem with [...a, last] that [...a] doesn't have? I still don't get it.

Since you don’t know when the iterator produced for …a will terminate, there’s no way to know when you need to stop iterating …a and move onto the next item last.

That statement is factually incorrect. There is a simple criteria to know when to terminate the iteration for a final rest element, which is when the iterator returns a result object with "done": true.

There is no condition to determine when to switch from a non-final rest element to some other element. That is a problem which needs to be addressed.

I'm similarly confused - there's no need to "determine when to switch"; we don't evaluate things in different contexts or anything. It just requires storage equal to the number of post-rest arguments; when you do hit the end, the things you're holding onto get assigned to the post-rest variable names. This is all done internally with a freshly-produced array; I don't think the timing of array-appending is even observable, so you shouldn't be able to tell that an item is appended only after later items are pulled from the source iterator.

I'm similarly confused by the wording you're using, tho, which suggests there may be a deeper communication mismatch - there's no "iterator produced for ...a". The ...a just indicates that you need to pull on the iterator being assigned to the destructuring pattern, and store the results that aren't claimed by other parts of the destructuring pattern into "a".

# Olivier Lalonde (8 years ago)

I think Olivier's point is that there is no way to know when you should

stop iterating in the case of [...a] either - hence the two cases are equivalently problematic, if at all. Pulling the last element out when the iteration stops is a different concern IMO which seems trivial to solve:

Correct.

The ...a just indicates that you need to pull on the iterator being

assigned to the destructuring pattern, and store the results that aren't claimed by other parts of the destructuring pattern into "a".

Yes, exactly. I'd understand the issue better if "a" had to be an arbitrary iterable but AFAICT it's alway an array and the RHS iterable always needs to be fully iterated regardless of whether "[...a, last]" syntax is supported or not.

# Logan Smyth (8 years ago)

FYI there's also some previous discussion on this here for those interested: esdiscuss/2015-October/044306, esdiscuss.org/topic/rest

# Caitlin Potter (8 years ago)

On Oct 3, 2016, at 10:54 PM, Tab Atkins Jr. <jackalmage at gmail.com> wrote:

On Sun, Oct 2, 2016 at 2:11 AM, Caitlin Potter <caitpotter88 at gmail.com> wrote:

On Oct 2, 2016, at 10:50 AM, Awal Garg <awalgarg at gmail.com> wrote:

On Oct 2, 2016, at 9:30 AM, Olivier Lalonde <olalonde at gmail.com> wrote: So what's the problem with [...a, last] that [...a] doesn't have? I still don't get it.

Since you don’t know when the iterator produced for …a will terminate, there’s no way to know when you need to stop iterating …a and move onto the next item last.

That statement is factually incorrect. There is a simple criteria to know when to terminate the iteration for a final rest element, which is when the iterator returns a result object with "done": true.

There is no condition to determine when to switch from a non-final rest element to some other element. That is a problem which needs to be addressed.

I'm similarly confused - there's no need to "determine when to switch"; we don't evaluate things in different contexts or anything. It just requires storage equal to the number of post-rest arguments; when you do hit the end, the things you're holding onto get assigned to the post-rest variable names. This is all done internally with a freshly-produced array; I don't think the timing of array-appending is even observable, so you shouldn't be able to tell that an item is appended only after later items are pulled from the source iterator.

I'm similarly confused by the wording you're using, tho, which suggests there may be a deeper communication mismatch - there's no "iterator produced for ...a". The ...a just indicates that you need to pull on the iterator being assigned to the destructuring pattern, and store the results that aren't claimed by other parts of the destructuring pattern into "a".

~TJ

"I'm similarly confused about your assertion that this idea needs to avoid a halting problem, because here's a way around this problem, not suggested by anyone else, with a non-negligible cost, so there"?

Really? I'm asking how they would alter IteratorBindingInitialization. It's not rocket science, but it's important for the discussion.

# Caitlin Potter (8 years ago)

On Oct 3, 2016, at 10:54 PM, Tab Atkins Jr. <jackalmage at gmail.com> wrote:

On Sun, Oct 2, 2016 at 2:11 AM, Caitlin Potter <caitpotter88 at gmail.com> wrote:

On Oct 2, 2016, at 10:50 AM, Awal Garg <awalgarg at gmail.com> wrote:

On Oct 2, 2016, at 9:30 AM, Olivier Lalonde <olalonde at gmail.com> wrote: So what's the problem with [...a, last] that [...a] doesn't have? I still don't get it.

Since you don’t know when the iterator produced for …a will terminate, there’s no way to know when you need to stop iterating …a and move onto the next item last.

That statement is factually incorrect. There is a simple criteria to know when to terminate the iteration for a final rest element, which is when the iterator returns a result object with "done": true.

There is no condition to determine when to switch from a non-final rest element to some other element. That is a problem which needs to be addressed.

I'm similarly confused - there's no need to "determine when to switch"; we don't evaluate things in different contexts or anything. It just requires storage equal to the number of post-rest arguments; when you do hit the end, the things you're holding onto get assigned to the post-rest variable names. This is all done internally with a freshly-produced array; I don't think the timing of array-appending is even observable, so you shouldn't be able to tell that an item is appended only after later items are pulled from the source iterator.

Also, this behaviour differs observably from the current behaviour, I believe. Consider:

try {
  var [...x, y, z] = <object where retrieving the last element throws>
} catch (e) {
  console.log(y); // initialized?
}

I'm fairly sure currently (ignoring the non-final rest element) everything would be bound except for z. But in this idea, it sounds like it wouldn't be.

So, another thing to deal with, I guess

# Cyril Auburtin (8 years ago)

I didn't understand everything discussed above, but to me, the only asymmetry between head and tail destructuring is for infinite iterators:

var [x] = neverEndingIterator vs var [...a, x] = neverEndingIterator // fails

in other cases plain arrays are quite symmetrical, they can be iterated from the start or the end. Finite iterators are iterable only from the start, but they would be 'expanded' in an array for tail destructuring.

So I think it would make sense to remove the "Uncaught SyntaxError: Rest element must be last element in array" error, and maybe just let it freeze if the iterable is infinite, that's programmer's responsibility

# Jason Orendorff (8 years ago)

On Tue, Oct 4, 2016 at 7:12 AM, Cyril Auburtin <cyril.auburtin at gmail.com>

wrote:

I didn't understand everything discussed above, but to me, the only asymmetry between head and tail destructuring is for infinite iterators:

var [x] = neverEndingIterator vs var [...a, x] = neverEndingIterator // fails

Read the post just before yours. Caitlin pointed out another difference.

# Cyril Auburtin (8 years ago)

Ah right, well caught :)

for arrays I forgot it's possible to do:

var a=[1,2,3,4,5,6], {[a.length-1]: last}=a; last

2016-10-04 15:33 GMT+02:00 Jason Orendorff <jason.orendorff at gmail.com>:

# Tab Atkins Jr. (8 years ago)

On Mon, Oct 3, 2016 at 8:06 PM, Caitlin Potter <caitpotter88 at gmail.com> wrote:

On Oct 3, 2016, at 10:54 PM, Tab Atkins Jr. <jackalmage at gmail.com> wrote:

On Sun, Oct 2, 2016 at 2:11 AM, Caitlin Potter <caitpotter88 at gmail.com> wrote:

On Oct 2, 2016, at 10:50 AM, Awal Garg <awalgarg at gmail.com> wrote:

On Oct 2, 2016, at 9:30 AM, Olivier Lalonde <olalonde at gmail.com> wrote: So what's the problem with [...a, last] that [...a] doesn't have? I still don't get it.

Since you don’t know when the iterator produced for …a will terminate, there’s no way to know when you need to stop iterating …a and move onto the next item last.

That statement is factually incorrect. There is a simple criteria to know when to terminate the iteration for a final rest element, which is when the iterator returns a result object with "done": true.

There is no condition to determine when to switch from a non-final rest element to some other element. That is a problem which needs to be addressed.

I'm similarly confused - there's no need to "determine when to switch"; we don't evaluate things in different contexts or anything. It just requires storage equal to the number of post-rest arguments; when you do hit the end, the things you're holding onto get assigned to the post-rest variable names. This is all done internally with a freshly-produced array; I don't think the timing of array-appending is even observable, so you shouldn't be able to tell that an item is appended only after later items are pulled from the source iterator.

I'm similarly confused by the wording you're using, tho, which suggests there may be a deeper communication mismatch - there's no "iterator produced for ...a". The ...a just indicates that you need to pull on the iterator being assigned to the destructuring pattern, and store the results that aren't claimed by other parts of the destructuring pattern into "a".

"I'm similarly confused about your assertion that this idea needs to avoid a halting problem, because here's a way around this problem, not suggested by anyone else, with a non-negligible cost, so there"?

Really? I'm asking how they would alter IteratorBindingInitialization. It's not rocket science, but it's important for the discussion.

Right, the details are important, but there's clearly no need to predict future behavior to achieve it; you can do this sort of thing in userland already.

Also, this behaviour differs observably from the current behaviour, I believe. Consider:

try {
  var [...x, y, z] = <object where retrieving the last element throws>
} catch (e) {
  console.log(y); // initialized?
}

I'm fairly sure currently (ignoring the non-final rest element) everything would be bound except for z. But in this idea, it sounds like it wouldn't be.

So, another thing to deal with, I guess

Ah, yes, so it is observable, good catch. Yes, the only reasonable way to do this is to have an N-element delay in assigning, where N is the number of post-rest arguments, and then only assign the final N arguments when the end of the iterator is hit. So in this case, both y and z would be uninitialized; the algo would be holding onto one pending element (to be assigned to y if the next pull returns a {done:true}) and the throw prevents it from doing that assignment.