Rest parameter syntax

# Felix Böhm (11 years ago)

Per definition, rest parameters always need to be at the end of a FormalParameterList. I was wondering if this limitation could be liftet.

Use-case: Most SQL libraries for node support passing query arguments to the query function (so SQL injections are avoided). The usual syntax is db.run("SELECT

  • FROM t WHERE foo=? AND cbar=?", "bar", "cfoo", function(err, row){…});

The number of arguments passed after the query depends on the number of question marks inside the query. It would be nice when an implementation could simply write db.run = function(query, ...params, cb){…} and avoid using the arguments object or Array#poping the last element of params.

This change would require two additions: Every Identifier past rest can't be another rest parameter or have a default value specified.

# Rick Waldron (11 years ago)

On Tue, Jun 12, 2012 at 10:38 AM, Felix Böhm <esdiscuss at feedic.com> wrote:

Per definition, rest parameters always need to be at the end of a FormalParameterList. I was wondering if this limitation could be liftet.

Consider:

function foo( a, b, ...others, c ) { return [ a, b, others, c ]; }

foo( 1, 2, 3, 4, 5, 6, 7, 8, 9 );

a b others [ 1, 2, [ 3, 4, 5, 6, 7, 8, 9 ] ]

How do you resolve where to stop claiming arguments as items in the rest param "others"?

# Russell Leggett (11 years ago)

On Tue, Jun 12, 2012 at 11:26 AM, Rick Waldron <waldron.rick at gmail.com>wrote:

On Tue, Jun 12, 2012 at 10:38 AM, Felix Böhm <esdiscuss at feedic.com> wrote:

Per definition, rest parameters always need to be at the end of a FormalParameterList. I was wondering if this limitation could be liftet.

Consider:

function foo( a, b, ...others, c ) { return [ a, b, others, c ]; }

foo( 1, 2, 3, 4, 5, 6, 7, 8, 9 );

a b others [ 1, 2, [ 3, 4, 5, 6, 7, 8, 9 ] ]

How do you resolve where to stop claiming arguments as items in the rest param "others"?

Well, I mean, technically speaking its no different than a very simple regex.

/(.)(.)(.*)(.)/.exec("123456789"); ["123456789", "1", "2", "345678", "9"]

/(.)(.)(.*)(.)/.exec("123"); ["123", "1", "2", "", "3"]

From that perspective, it seems pretty deterministic and easy to explain.

There's probably a reason I'm not thinking of, though.

# Herby Vojčík (11 years ago)

Rick Waldron wrote:

On Tue, Jun 12, 2012 at 10:38 AM, Felix Böhm <esdiscuss at feedic.com <mailto:esdiscuss at feedic.com>> wrote:

Per definition, rest parameters always need to be at the end of a
FormalParameterList. I was wondering if this limitation could be liftet.

Consider:

function foo( a, b, ...others, c ) { return [ a, b, others, c ]; }

foo( 1, 2, 3, 4, 5, 6, 7, 8, 9 );

a b others [ 1, 2, [ 3, 4, 5, 6, 7, 8, 9 ] ]

How do you resolve where to stop claiming arguments as items in the rest param "others"?

[ 1, 2, [ 3, 4, 5, 6, 7, 8 ] ] As I understand Felix's idea, it's that the parameters after ...rest eat up the last ones. Using last (or last few) params is the often used (not that I like it) pattern - node.js is full of it since callback is always last.

But I understand there are problems. First, what with optional params after ...rest. And the second, how to parse it when foo(1, 2) called?

# Herby Vojčík (11 years ago)

Felix Böhm wrote:

Per definition, rest parameters always need to be at the end of a FormalParameterList. I was wondering if this limitation could be liftet.

Use-case: Most SQL libraries for node support passing query arguments to the query function (so SQL injections are avoided). The usual syntax is db.run("SELECT * FROM t WHERE foo=? AND cbar=?", "bar", "cfoo", function(err, row){…});

The number of arguments passed after the query depends on the number of question marks inside the query. It would be nice when an implementation could simply write db.run = function(query, ...params, cb){…} and avoid using the arguments object or Array#poping the last element of params.

BTW, isn't this possible with comprehensions in parameter list:

db.run = function (query, ...[...params, cb]) { /* body */ }

If yes, problem solved.

# T.J. Crowder (11 years ago)

On 12 June 2012 16:42, Herby Vojčík <herby at mailbox.sk> wrote:

But I understand there are problems. First, what with optional params after ...rest. And the second, how to parse it when foo(1, 2) called?

I would think with

function foo(a, b, ...others, c) { }

then given

foo(1, 2);

...within foo a is 1, b is 2, others is empty, and c is undefined. E.g., args prior to the restargs get priority over args after rest args. This is consistent with

foo(1);

...where within foo a is 1, b is undefined, others is empty, and c is undefined.

It does seem as though it can be deterministic, and pretty easy to explain. Which isn't necessarily an endorsement, just identifying that this particular issue doesn't immediately seem like a roadblock.

-- T.J.

# Herby Vojčík (11 years ago)

T.J. Crowder wrote:

On 12 June 2012 16:42, Herby Vojčík <herby at mailbox.sk <mailto:herby at mailbox.sk>> wrote:

But I understand there are problems. First, what with optional
params after ...rest. And the second, how to parse it when foo(1, 2)
called?

I would think with

function foo(a, b, ...others, c) { }

then given

foo(1, 2);

...within foo a is 1, b is 2, others is empty, and c is undefined. E.g., args prior to the restargs get priority over args after rest args. This is consistent with

foo(1);

...where within foo a is 1, b is undefined, others is empty, and c is undefined.

It does seem as though it can be deterministic, and pretty easy to explain. Which isn't necessarily an endorsement, just identifying that

function foo (a, b, ...rest, c, d) { ... } foo(1, 2, 3)

What here?

Yes, [1, 2, [], 3, undefined] is probably the most logical one. But then d is not the last one (yes, it is only last one when there is at least four of them).

# T.J. Crowder (11 years ago)

On 12 June 2012 17:03, Herby Vojčík <herby at mailbox.sk> wrote:

function foo (a, b, ...rest, c, d) { ... } foo(1, 2, 3)

What here?

Yes, [1, 2, [], 3, undefined] is probably the most logical one. But then d is not the last one (yes, it is only last one when there is at least four of them).

Yeah, I was regretting not addressing that case. :-)

I'd say your interpretation (1, 2, [], 3, undefined) would be the best answer, and easiest to explain. There's a temptation to suggest that you could reverse c and d (1, 2, [], undefined, 3), but that way surely lies madness...

-- T.J.

# Allen Wirfs-Brock (11 years ago)

On Jun 12, 2012, at 8:49 AM, Herby Vojčík wrote:

...

BTW, isn't this possible with comprehensions in parameter list:

db.run = function (query, ...[...params, cb]) { /* body */ }

If yes, problem solved.

Current draft requires an identifier after ... in a rest position

# Felix Böhm (11 years ago)

As written before:

function foo(a, b, ...others, c){ … }

behaves the same as

function foo(a, b, ...others){ var c = others.pop(); }

When the number of parameters following the rest parameter is greater or equal the number of passed arguments, the rest parameter is an empty array and the parameters get assigned in order until there are no more arguments. Unassigned parameters default to undefined.

When the number of arguments is greater than the number of parameters following the rest parameter, then the first(argument-number - parameter-number) arguments are passed as the rest parameter.

2012/6/12 Herby Vojčík <herby at mailbox.sk>

# Allen Wirfs-Brock (11 years ago)

We have had several previous discussions about such possibilities on this list.

Bottom line, is that destructuring (including formal parameters) could be moved towards something that is more like generalized pattern matching. However, it isn't clear that the additional specification, implementation, and usage complexity is justified at this time. For ES6 we have the most important use cases covered. If experience with it suggests that some generalizations would add real value we can consider them for future editions.

# T.J. Crowder (11 years ago)

On 12 June 2012 17:21, Felix Böhm <esdiscuss at feedic.com> wrote:

As written before:

function foo(a, b, ...others, c){ … }

behaves the same as

function foo(a, b, ...others){ var c = others.pop(); }

Sadly, it's not as simple to emulate

function foo(a, b, ...others, c, d) { }

...using pop. But obviously it can be done one way or another.

When the number of parameters following the rest parameter is greater or

equal the number of passed arguments, the rest parameter is an empty array and the parameters get assigned in order until there are no more arguments. Unassigned parameters default to undefined.

When the number of arguments is greater than the number of parameters following the rest parameter, then the first(argument-number - parameter-number) arguments are passed as the rest parameter.

Yes, the rule doesn't seem particularly complicated.

I've definitely seen (and very occasionally used) this sort of thing in the real world, though not nearly to Node's extent. The beginning and end of the args list are the only fixed points.

On 12 June 2012 17:21, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

For ES6 we have the most important use cases covered. If experience with it suggests that some generalizations would add real value we can consider them for future editions.

Very true, no reason it couldn't be loosened up in ES7 or whatever. And yet, if not yet implemented in its current form (I have no idea what the extent of implementation is), the complexity increment does not seem large. As Felix points out, the algorithm isn't exactly complicated.

-- T.J.

# Brendan Eich (11 years ago)

T.J. Crowder wrote:

Very true, no reason it couldn't be loosened up in ES7 or whatever.

Right, this is a virtue.

And yet, if not yet implemented in its current form (I have no idea what the extent of implementation is), the complexity increment does not seem large.

Here you went down the bad path.

It was not the last cookie I ate that made me fat. Every little "wafer-thin" (cf. MPTMoL) increment hurts. Every single one.

As Felix points out, the algorithm isn't exactly complicated.

More bad-path words, ignore the little horned red guy on your shoulder.

This is a marginal use-case, you said so yourself. It can take the long way 'round.

# Rick Waldron (11 years ago)

snip

But I understand there are problems. First, what with optional params after ...rest. And the second, how to parse it when foo(1, 2) called?

There was a lengthy thread about this, here: esdiscuss/2012-April/022256

Rick

ps. This is an alternate, nested thread view: old.nabble.com/Fwd%3A-undefined-being-treated-as-a-missing-optional-argument-tt33672515.html

# T.J. Crowder (11 years ago)

On 12 June 2012 18:00, Brendan Eich <brendan at mozilla.org> wrote:

T.J. Crowder wrote:

And yet, if not yet implemented in its current form (I have no idea what the extent of implementation is), the complexity increment does not seem large.

Here you went down the bad path.

It was not the last cookie I ate that made me fat. Every little "wafer-thin" (cf. MPTMoL) increment hurts. Every single one.

As Felix points out, the algorithm isn't exactly complicated.

More bad-path words, ignore the little horned red guy on your shoulder.

This sort of argument can almost always be applied. Again, not saying I really endorse the embedded varargs, but I don't think this is a persuasive argument against on its own.

-- T.J.

# Brendan Eich (11 years ago)

T.J. Crowder wrote:

On 12 June 2012 18:00, Brendan Eich <brendan at mozilla.org <mailto:brendan at mozilla.org>> wrote:

T.J. Crowder wrote:

    And yet, if not yet implemented in its current form (I have no
    idea what the extent of implementation is), the complexity
    increment does not seem large.


Here you went down the bad path.

It was not the last cookie I ate that made me fat. Every little
"wafer-thin" (cf. MPTMoL) increment hurts. Every single one.

    As Felix points out, the algorithm isn't exactly complicated.


More bad-path words, ignore the little horned red guy on your
shoulder.

This sort of argument can almost always be applied. Again, not saying I really endorse the embedded varargs, but I don't think this is a persuasive argument against on its own.

You are correct!

The context matters. In that context, your argument that "the complexity increment does not seem large" is also not a persuasive argument for adding that increment, and I call it worse: a sign (in context of rest as drafted for ES6) that you're going too far.

Red guy still on your shoulder :-P.

# T.J. Crowder (11 years ago)

On 12 June 2012 18:14, Brendan Eich <brendan at mozilla.org> wrote:

Red guy still on your shoulder :-P.

I think you're seeing things. ;-) But seriously, I don't really have a horse in this race (at all, I was just exploring the concept -- seems that's already been done), happy to leave it at that.

-- T.J.

# Felix Böhm (11 years ago)

Another nice place for this syntax would be destructuring: If you want to get the last elements of an array, you might want to simply use

[...arr, foo, bar] = arr;

I really like that syntax. And in the end, that's what

function(...arr, foo, bar){…}

is doing. The difference to

bar = arr.pop(); foo = arr.pop();

is that foo is preferred when only one value is available. Written in the JS of today, you'll need to write

if(arr.length > 0){ if(arr.length !== 1){ bar = arr.pop(); } foo = arr.pop(); }

And it'll get more complicated with every added variable.

It's quite interesting that Herby used that syntax with the only feedback being that he can't use destructuring at that place :D

@Rick: I don't get your point. Of course, undefined should be treated as any other value, everything else would be confusing. Or what were you referring to?

As I've already written, parameters after rest can't have default values, which (partially) fixes the issue of optional parameters.

2012/6/12 T.J. Crowder <tj at crowdersoftware.com>

# Rick Waldron (11 years ago)

On Tue, Jun 12, 2012 at 2:56 PM, Felix Böhm <esdiscuss at feedic.com> wrote:

Another nice place for this syntax would be destructuring: If you want to get the last elements of an array, you might want to simply use

[...arr, foo, bar] = arr;

I really like that syntax. And in the end, that's what

function(...arr, foo, bar){…}

is doing. The difference to

bar = arr.pop(); foo = arr.pop();

is that foo is preferred when only one value is available. Written in the JS of today, you'll need to write

if(arr.length > 0){ if(arr.length !== 1){ bar = arr.pop(); } foo = arr.pop(); }

And it'll get more complicated with every added variable.

It's quite interesting that Herby used that syntax with the only feedback being that he can't use destructuring at that place :D

@Rick: I don't get your point. Of course, undefined should be treated as any other value, everything else would be confusing. Or what were you referring to?

Revisit...

function foo( a, b, ...others, c ) { return [ a, b, others, c ]; }

foo( 1, 2, 3, 4, 5, 6, 7, 8, 9 );

It's clear to me that you want any formal params that follow a ...rest to pop values off the arguments list until the params are satisfied, ie. c = 9.

The context of my question assumed there was a desire to avoid the complexity and that the discussion had already reached consensus on only allowing rest params at the end of a formal parameter list.

# Brendan Eich (11 years ago)

Rick Waldron wrote:

The context of my question assumed there was a desire to avoid the complexity and that the discussion had already reached consensus on only allowing rest params at the end of a formal parameter list.

That's the safe play for ES6, which must be prototyped and drafted this year, pretty much limiting new complexity.

As noted in the other thread, we're now talking about a pattern language for matching (refutably or irrefutably), which was proposed last year:

strawman:pattern_matching

Happy to use es-discuss to work out something richer than ES6 rest-only-at-end. I write this not to nag anyone, just to make my own position clear in case it sounded like I was against the idea of richer patterns than rest and destructuring as currently proposed or drafted. I'm not! Let's discuss.