Array tail destructuring
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.
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
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?
No, it calls [Symbol.iterator]()
on the RHS iterable, using the iterator
protocol. It has nothing to do with arrays.
So what's the problem with [...a, last]
that [...a]
doesn't have? I
still don't get it.
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.
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
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 itemlast
.
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.
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 itemlast
.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 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.
FYI there's also some previous discussion on this here for those interested: esdiscuss/2015-October/044306, esdiscuss.org/topic/rest
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 itemlast
.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.
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 itemlast
.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
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
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
vsvar [...a, x] = neverEndingIterator // fails
Read the post just before yours. Caitlin pointed out another difference.
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 itemlast
.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.
It was possibly already discussed, but why isn't:
supported?
I don't see the problem, as long as there's one ... only