T.J. Crowder (2017-08-03T11:00:24.000Z)
On Thu, Aug 3, 2017 at 10:18 AM, James Browning
<thejamesernator at gmail.com> wrote:
>
> The 1, 1 would happen if you decided that `[a, ...rest, b]` read
> in both directions (although personally I'm not a fan of this
> approach)...

Gotcha. Yeah, having it duplicate things would seem wrong. :-)

> One option could be (although I don't like it either) to allow
> the rest operator to have a direction...

I'd say: Keep it simple. Left-to-right, non-duplicating, non-greedy with
respect to non-rest bindings, greedy otherwise:

```js
function *source(len) {
    for (let n = 1; n <= len; ++n) {
    yield n;
  }
}
function test(len) {
  const [ a, ...rest, b, c ] = source(len);
  console.log("With " + len + ":", a, rest, b, c);
}
test(0); // With 0: undefined, [], undefined, undefined
test(1); // With 1: 1, [], undefined, undefined
test(2); // With 2: 1, [], 2, undefined
test(3); // With 3: 1, [], 2, 3
test(4); // With 4: 1, [2], 3, 4
test(5); // With 5: 1, [2, 3], 4, 5
```

On Thu, Aug 3, 2017 at 10:29 AM, Andy Earnshaw
<andyearnshaw at gmail.com> wrote:
>
> If you think of rest as "everything else" (which is what it already is)
then this feels pretty natural and is easy to reason about.

Exactly. (And as you say, hardly original with me.)

A pragmatic approach could simply consume the rest of the iterable into the
rest binding and then if there are more bindings after it, move those
entries into them:

```js
function assignToBindings(bindings, iterator) {
    let bindingIndex = 0;
    let currentBinding;
    let e;

    // Consume bindings prior to rest
    while ((currentBinding = bindings[bindingIndex++]) &&
!currentBinding.isRest) {
        e = iterator.next();
        if (e.done) {
            return;
        }
        currentBinding.value = e.value;
    }
    if (!currentBinding) {
        return; // Out of bindings
    }

    // Read to the end into the rest binding
    assert(currentBinding.isRest, "hit rest binding");
    const rest = currentBinding.value;
    while (!(e = iterator.next()).done) {
        rest.push(e.value);
    }
    if (bindingIndex >= bindings.length) {
        return; // rest binding was last binding
    }

    // Move trailing entries out of the rest binding into the trailing
bindings
    const restLength = Math.max(0, rest.length - (bindings.length -
bindingIndex));
    for (let restIndex = restLength; restIndex < rest.length; ++restIndex) {
        bindings[bindingIndex++].value = rest[restIndex];
    }
    rest.length = restLength;
}
```

[Fiddle with examples/tests](https://jsfiddle.net/p35vt5yc/).

-- T.J. Crowder
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20170803/4fdbf744/attachment.html>
tj.crowder at farsightsoftware.com (2017-08-03T14:46:46.298Z)
On Thu, Aug 3, 2017 at 10:18 AM, James Browning
<thejamesernator at gmail.com> wrote:
>
> The 1, 1 would happen if you decided that `[a, ...rest, b]` read
> in both directions (although personally I'm not a fan of this
> approach)...

Gotcha. Yeah, having it duplicate things would seem wrong. :-)

> One option could be (although I don't like it either) to allow
> the rest operator to have a direction...

I'd say: Keep it simple. Left-to-right, non-duplicating, non-greedy with
respect to non-rest bindings, greedy otherwise:

```js
function *source(len) {
    for (let n = 1; n <= len; ++n) {
    yield n;
  }
}
function test(len) {
  const [ a, ...rest, b, c ] = source(len);
  console.log("With " + len + ":", a, rest, b, c);
}
test(0); // With 0: undefined, [], undefined, undefined
test(1); // With 1: 1, [], undefined, undefined
test(2); // With 2: 1, [], 2, undefined
test(3); // With 3: 1, [], 2, 3
test(4); // With 4: 1, [2], 3, 4
test(5); // With 5: 1, [2, 3], 4, 5
```

On Thu, Aug 3, 2017 at 10:29 AM, Andy Earnshaw
<andyearnshaw at gmail.com> wrote:
>
> If you think of rest as "everything else" (which is what it already is)
> then this feels pretty natural and is easy to reason about.

Exactly. (And as you say, hardly original with me.)

A pragmatic approach could simply consume the rest of the iterable into the
rest binding and then if there are more bindings after it, move those
entries into them:

```js
function assignToBindings(bindings, iterator) {
    let bindingIndex = 0;
    let currentBinding;
    let e;

    // Consume bindings prior to rest
    while ((currentBinding = bindings[bindingIndex++]) && !currentBinding.isRest) {
        e = iterator.next();
        if (e.done) {
            return;
        }
        currentBinding.value = e.value;
    }
    if (!currentBinding) {
        return; // Out of bindings
    }

    // Read to the end into the rest binding
    assert(currentBinding.isRest, "hit rest binding");
    const rest = currentBinding.value;
    while (!(e = iterator.next()).done) {
        rest.push(e.value);
    }
    if (bindingIndex >= bindings.length) {
        return; // rest binding was last binding
    }

    // Move trailing entries out of the rest binding into the trailing bindings
    const restLength = Math.max(0, rest.length - (bindings.length - bindingIndex));
    for (let restIndex = restLength; restIndex < rest.length; ++restIndex) {
        bindings[bindingIndex++].value = rest[restIndex];
    }
    rest.length = restLength;
}
```

[Fiddle with examples/tests](https://jsfiddle.net/p35vt5yc/).

-- T.J. Crowder