[proposal] Function calls, syntax sugar

# Andrew Fedoniouk (12 years ago)

Quite often I see constructions like this:

foo({one:1,two:2});

so call of function with single parameter - object literal. Idiom named "Poor man named arguments passing"

Idea is to extend existing JS/ES syntax calls to support simple form of the call above:

foo {one:1,two:2 };

Semi-formally that syntax looks like:

function-call:
   <name-token> '(' <parameter-list> ')' // existing form
   <name-token> <object-literal> // proposal

This syntax extension will not break correct pre-ES6 code as far as I can tell. But I am not sure about new ES6 arrivals.

In fact we can add even more generic form of function call:

foo 1,2,"three";

that is an equivalent of foo(1,2,"three");

In this case grammar may look like:

function-call:
   <name-token> '(' <parameter-list> ')' // existing form
   <name-token> <parameter-list> // proposal

Where <parameter-list> is comma , separated list of expressions.

Just to reduce that bracketing noise in syntax.

# Axel Rauschmayer (12 years ago)

I like the idea, maybe we could do the following:

foo(posArg1, posArg2, name1: x, name2: y)

as syntactic sugar for:

foo(posArg1, posArg2, { name1: x, name2: y })

Axel

# Rick Waldron (12 years ago)
foo {one:1,two:2 };

Semi-formally that syntax looks like:

function-call:
   <name-token> '(' <parameter-list> ')' // existing form
   <name-token> <object-literal> // proposal

Given:

function foo(){ return 1; }

This is a valid function call:

foo
()

// 1;

But this is valid, too (though, not a function call):

foo  <-- identifier
{} <-- empty block

// function foo() { return 1; }

Also, I could be wrong, but wouldn't there need to be a way to disambiguate UnaryExpression?

typeof foo { one: 1, two: 2 }

Maybe this is not an issue, or easily dealt with?

We can add even more generic form of function call:

foo 1,2,"three";

The equivalent to:

foo(1, 2, bar("a", "b"))

is...

foo 1, 2, bar "a", "b"

?

CoffeeScript has this problem, too.

# Rick Waldron (12 years ago)

Andrew:

Semi-formally that syntax looks like:

function-call:
   <name-token> '(' <parameter-list> ')' // existing form
   <name-token> <object-literal> // proposal

Existing grammar:

CallExpression Arguments

Arguments :
  ( )
  ( ArgumentList )

To add ObjectLiteral, at very least the grammar would need to have a NoLineTerminator between CallExpression and Arguments, which breaks extant code.

# Claus Reinke (12 years ago)

A slightly less ambitious suggestion:

consider f() as syntax for the implicit arguments array
(which, as of ES6, can be considered deprecated), then
make the parens in this syntax optional

In other words, you could write

f 1 // single parameter
f(1,2)    // single parameter, implicit arguments pseudo-array
f [1,2]    // single parameter, explicit array

Things get more interesting when you consider currying (functions returning functions):

f(1)(2) // conventional, implicit arguments pseudo-arrays
f 1 2    // paren-free, single parameters, no arrays

For your nested call example, you'd have the choice between

foo(1, 2, bar("a", "b"))    //uncurried, implicit pseudo-arrays
foo[1, 2, bar["a", "b"]]    // uncurried, explicit arrays
foo 1 2 (bar "a" "b")    // curried, single parameters

In the latter variant, () are used for grouping, consistent with their use in the rest of the language.

Nice as this would be, I don't know whether this can be fitted into ES grammar and ASI... (probably not?).

Claus

PS. could es-discuss-owner please check their mailbox (and update the mailing list info page)?

# Rick Waldron (12 years ago)

On Fri, Jul 12, 2013 at 11:07 AM, Claus Reinke <claus.reinke at talk21.com>wrote:

A slightly less ambitious suggestion:

consider f() as syntax for the implicit arguments array
(which, as of ES6, can be considered deprecated), then
make the parens in this syntax optional

(snip)

In other words, you could write

f 1 // single parameter
f(1,2)    // single parameter, implicit arguments pseudo-array

This breaks CallExpression Arguments...

Given:

function f(a, b) {
  return [a, b];
}

Currently:

f(1, 2); // [1, 2]

Whereas...

// single parameter, implicit arguments pseudo-array:
f(1, 2);

a would be magically be treated like a ...rest param that wasn't really an array, but instead a implicit arguments pseudo-array?

// [[1, 2], undefined]
f [1,2]    // single parameter, explicit array

(snip)

For your nested call example, you'd have the choice between

foo(1, 2, bar("a", "b"))    //uncurried, implicit pseudo-arrays
foo[1, 2, bar["a", "b"]]    // uncurried, explicit arrays

Optional parens for CallExpression Arguments or MemberExpression Arguments results in convoluted argument lists. The solutions shown above using [] also create ambiguity with:

  • MemberExpression[ Expression ]
  • CallExpression[ Expression ]

Given:

function foo(value) {
  return value;
}
foo.prop = "Some data";

Currently:

foo("prop"); // "prop"

foo["prop"]; // "Some data"

Whereas...

foo[1, 2, bar["a", "b"]]    // uncurried, explicit arrays

foo["prop"] // What does this return?
# Claus Reinke (12 years ago)

function f(a, b) { return [a, b]; }

Currently:

f(1, 2); // [1, 2]

Whereas...

// single parameter, implicit arguments pseudo-array: f(1, 2);

|a| would be magically be treated like a ...rest param that wasn't really an array, but instead a implicit arguments pseudo-array?

// [[1, 2], undefined]

No, just another way to describe the current situation, where

function f() {return [...arguments]}    // pseudo code
f(1,2) // [1,2]

or, if we make the arguments explicit

function f(...arguments) {return [...arguments]}    // pseudo code
f(1,2) // [1,2]

and explicit formal parameters would be destructured from arguments, so

function f(a,b) {return [a,b]} 
f(1,2) // [1,2]

The solutions shown above using [] also create ambiguity with:

  • MemberExpression[ Expression ]
  • CallExpression[ Expression ]

Given:

function foo(value) { return value; } foo.prop = "Some data";

Currently:

foo("prop"); // "prop"

foo["prop"]; // "Some data"

ah, yes, I knew there had to be a serious flaw somewhere... sigh

Claus

# Andrew Fedoniouk (12 years ago)

On Fri, Jul 12, 2013 at 6:45 AM, Rick Waldron <waldron.rick at gmail.com> wrote:

On Fri, Jul 12, 2013 at 12:22 AM, Andrew Fedoniouk <news at terrainformatica.com> wrote:

Quite often I see constructions like this:

foo({one:1,two:2});

so call of function with single parameter - object literal. Idiom named "Poor man named arguments passing"

Idea is to extend existing JS/ES syntax calls to support simple form of the call above:

foo {one:1,two:2 };

Semi-formally that syntax looks like:

function-call: <name-token> '(' <parameter-list> ')' // existing form <name-token> <object-literal> // proposal

Given:

function foo(){ return 1; }

This is a valid function call:

foo ()

// 1;

But this is valid, too (though, not a function call):

foo <-- identifier {} <-- empty block

// function foo() { return 1; }

This construction

foo {};

is an equivalent of:

foo({});

but not

foo();

To call function with empty param list you still need empty '('')' brackets.

Also, I could be wrong, but wouldn't there need to be a way to disambiguate UnaryExpression?

typeof foo { one: 1, two: 2 }

Maybe this is not an issue, or easily dealt with?

This is not an issue. Parsed exactly the same as: typeof foo({ one: 1, two: 2 })

-- Andrew Fedoniouk.

terrainformatica.com

# Andrew Fedoniouk (12 years ago)

On Fri, Jul 12, 2013 at 7:02 AM, Rick Waldron <waldron.rick at gmail.com> wrote:

On Fri, Jul 12, 2013 at 12:22 AM, Andrew Fedoniouk <news at terrainformatica.com> wrote:

Quite often I see constructions like this:

foo({one:1,two:2});

so call of function with single parameter - object literal. Idiom named "Poor man named arguments passing"

Idea is to extend existing JS/ES syntax calls to support simple form of the call above:

foo {one:1,two:2 };

Semi-formally that syntax looks like:

function-call: <name-token> '(' <parameter-list> ')' // existing form <name-token> <object-literal> // proposal

Existing grammar:

CallExpression Arguments

Arguments : ( ) ( ArgumentList )

To add ObjectLiteral, at very least the grammar would need to have a NoLineTerminator between CallExpression and Arguments, which breaks extant code.

Yes, NoLineTerminator is required for non-strict mode (that ugly semicolon elision strikes again)

Even in non-strict mode this code:

function foo(obj) { return 1; }

var c = foo { one:1 };

produces parsing error before the '{' - "semicolon required".

The same kind of error is in this case too:

function foo(p1,p2) { return 1; }

var c = foo 1,2;

So that syntax change will be backward compatible - it will not change semantic of existing valid code.

-- Andrew Fedoniouk.

terrainformatica.com

# Tab Atkins Jr. (12 years ago)

On Thu, Jul 11, 2013 at 9:22 PM, Andrew Fedoniouk <news at terrainformatica.com> wrote:

Quite often I see constructions like this:

foo({one:1,two:2});

so call of function with single parameter - object literal. Idiom named "Poor man named arguments passing"

Idea is to extend existing JS/ES syntax calls to support simple form of the call above:

foo {one:1,two:2 };

Semi-formally that syntax looks like:

function-call: <name-token> '(' <parameter-list> ')' // existing form <name-token> <object-literal> // proposal

This syntax extension will not break correct pre-ES6 code as far as I can tell. But I am not sure about new ES6 arrivals.

In fact we can add even more generic form of function call:

foo 1,2,"three";

that is an equivalent of foo(1,2,"three");

In this case grammar may look like:

function-call: <name-token> '(' <parameter-list> ')' // existing form <name-token> <parameter-list> // proposal

Where <parameter-list> is comma ',' separated list of expressions.

Just to reduce that bracketing noise in syntax.

I'm not a fan of this, because it only gets us part of the way toward the benefits of real named parameter support, and makes it even less likely that we'll actually get to the end. Python has it good - every argument can be given either by position or by name, and you can collect both extra positional arguments and extra named arguments. I'd prefer figuring out a syntax for argument lists that gives us the same argument-list power as Python.

# Andrew Fedoniouk (12 years ago)

On Fri, Jul 12, 2013 at 11:20 AM, Tab Atkins Jr. <jackalmage at gmail.com> wrote:

On Thu, Jul 11, 2013 at 9:22 PM, Andrew Fedoniouk <news at terrainformatica.com> wrote:

Quite often I see constructions like this:

foo({one:1,two:2});

so call of function with single parameter - object literal. Idiom named "Poor man named arguments passing"

Idea is to extend existing JS/ES syntax calls to support simple form of the call above:

foo {one:1,two:2 };

Semi-formally that syntax looks like:

function-call: <name-token> '(' <parameter-list> ')' // existing form <name-token> <object-literal> // proposal

This syntax extension will not break correct pre-ES6 code as far as I can tell. But I am not sure about new ES6 arrivals.

....

Just to reduce that bracketing noise in syntax.

I'm not a fan of this, because it only gets us part of the way toward the benefits of real named parameter support, and makes it even less likely that we'll actually get to the end. Python has it good - every argument can be given either by position or by name, and you can collect both extra positional arguments and extra named arguments. I'd prefer figuring out a syntax for argument lists that gives us the same argument-list power as Python.

Adding Python'ic way of handling parameters requires substantial change of existing runtime architecture ('arguments' and around).

In contrary proposed

foo {one:1,two:2 };

requires just syntax change .

-- Andrew Fedoniouk.

terrainformatica.com

# Rick Waldron (12 years ago)

On Fri, Jul 12, 2013 at 1:42 PM, Andrew Fedoniouk <news at terrainformatica.com

wrote:

On Fri, Jul 12, 2013 at 6:45 AM, Rick Waldron <waldron.rick at gmail.com> wrote:

On Fri, Jul 12, 2013 at 12:22 AM, Andrew Fedoniouk <news at terrainformatica.com> wrote:

Quite often I see constructions like this:

foo({one:1,two:2});

so call of function with single parameter - object literal. Idiom named "Poor man named arguments passing"

Idea is to extend existing JS/ES syntax calls to support simple form of the call above:

foo {one:1,two:2 };

Semi-formally that syntax looks like:

function-call: <name-token> '(' <parameter-list> ')' // existing form <name-token> <object-literal> // proposal

Given:

function foo(){ return 1; }

This is a valid function call:

foo ()

// 1;

But this is valid, too (though, not a function call):

foo <-- identifier {} <-- empty block

// function foo() { return 1; }

This construction

foo {};

is an equivalent of:

foo({});

but not

foo();

Right, I get that... but what I'm telling you is that your proposal doesn't work:

foo {}

Same as

foo({})

So, that means:

foo {}

should be the same as

foo ({})

...Because this is a valid function call.

But it's not the same and cannot be defined as the same, because ASI put a semi colon at the end of |foo| and {} is a valid empty block. Changing this would surely be web breaking.

Rick

To call function with empty param list you still need empty '('')' brackets.

Also, I could be wrong, but wouldn't there need to be a way to disambiguate UnaryExpression?

typeof foo { one: 1, two: 2 }

Maybe this is not an issue, or easily dealt with?

This is not an issue. Parsed exactly the same as: typeof foo({ one: 1, two: 2 })

Right, but again:

function foo() {return 1;}

typeof foo ({})

"number"

...Because this is the same as

typeof foo({})

So you need a [no LineTerminator here], which unnecessarily complicates:

CallExpression Arguments CallExpression[no LineTerminator here]ObjectLiteral

# Rick Waldron (12 years ago)

On Fri, Jul 12, 2013 at 2:08 PM, Andrew Fedoniouk <news at terrainformatica.com

wrote:

On Fri, Jul 12, 2013 at 7:02 AM, Rick Waldron <waldron.rick at gmail.com> wrote:

On Fri, Jul 12, 2013 at 12:22 AM, Andrew Fedoniouk <news at terrainformatica.com> wrote:

Quite often I see constructions like this:

foo({one:1,two:2});

so call of function with single parameter - object literal. Idiom named "Poor man named arguments passing"

Idea is to extend existing JS/ES syntax calls to support simple form of the call above:

foo {one:1,two:2 };

Semi-formally that syntax looks like:

function-call: <name-token> '(' <parameter-list> ')' // existing form <name-token> <object-literal> // proposal

Existing grammar:

CallExpression Arguments

Arguments : ( ) ( ArgumentList )

To add ObjectLiteral, at very least the grammar would need to have a NoLineTerminator between CallExpression and Arguments, which breaks extant code.

Yes, NoLineTerminator is required for non-strict mode (that ugly semicolon elision strikes again)

Even in non-strict mode this code:

function foo(obj) { return 1; }

var c = foo { one:1 };

produces parsing error before the '{' - "semicolon required".

Of course it does, but again, you're ignoring my point about ASI. Invocation parens (and arguments list) are allowed to be on the a following line:

function foo(value) { return value; }

foo

("hi!")

// "hi!";

And again, the opening and closing curly brace synatx that you think is an ObjectLiteral becomes a valid Block on the next line (because invocation parens and argument lists CAN be on the next line):

function foo(obj) { return 1; }

var c = foo { one:1 }; <-- valid block, containing a LabelStatement // 1

c; // function foo(obj) { return 1; }

The same kind of error is in this case too:

function foo(p1,p2) { return 1; }

var c = foo 1,2;

Still the same reason I've said over several responses.

function foo(p1,p2) { return 1; }

var c = foo 1,2;

// 2 c; // function foo(obj) { return 1; }

So that syntax change will be backward compatible - it will not change semantic of existing valid code.

Contrary to the evidence I've provided several times in several messages?

# Andrew Fedoniouk (12 years ago)

On Fri, Jul 12, 2013 at 2:06 PM, Rick Waldron <waldron.rick at gmail.com> wrote:

On Fri, Jul 12, 2013 at 1:42 PM, Andrew Fedoniouk <news at terrainformatica.com> wrote:

...

This construction

foo {};

is an equivalent of:

foo({});

but not

foo();

Right, I get that... but what I'm telling you is that your proposal doesn't work:

foo {}

Same as

foo({})

So, that means:

foo {}

should be the same as

foo ({})

...Because this is a valid function call.

But it's not the same and cannot be defined as the same, because ASI put a semi colon at the end of |foo| and {} is a valid empty block. Changing this would surely be web breaking.

Seems like I am not getting that famous ASI thing.

I do not understand why here:

foo (exp);

there is no semicolon injected. It rather should be this:

foo; (exp);

if that ASI thing has any traces of logic behind.

BTW: what about "use strict" ? Does it govern ASI parsing rules? If not then why?

# Tab Atkins Jr. (12 years ago)

On Fri, Jul 12, 2013 at 3:55 PM, Andrew Fedoniouk <news at terrainformatica.com> wrote:

Seems like I am not getting that famous ASI thing.

I do not understand why here:

foo (exp);

there is no semicolon injected. It rather should be this:

foo; (exp);

if that ASI thing has any traces of logic behind.

It has a very simple logic, just not the one you're assuming.

You're probably thinking that the rule is "if a line doesn't end in a semicolon, and you can insert one without causing this line or the next to have a parse error, do so". The actual rule is "if a line doesn't end in a semicolon, attempt to join it with the following line. If that causes a syntax error, insert a semicolon and try again".

In other words, ASI only happens when a semicolon is required, not when one is possible.

BTW: what about "use strict" ? Does it govern ASI parsing rules? If not then why?

No, strict mode has no effect on ASI.

# Andrew Fedoniouk (12 years ago)

On Fri, Jul 12, 2013 at 4:17 PM, Tab Atkins Jr. <jackalmage at gmail.com> wrote:

On Fri, Jul 12, 2013 at 3:55 PM, Andrew Fedoniouk <news at terrainformatica.com> wrote:

Seems like I am not getting that famous ASI thing.

I do not understand why here:

foo (exp);

there is no semicolon injected. It rather should be this:

foo; (exp);

if that ASI thing has any traces of logic behind.

It has a very simple logic, just not the one you're assuming.

You're probably thinking that the rule is "if a line doesn't end in a semicolon, and you can insert one without causing this line or the next to have a parse error, do so". The actual rule is "if a line doesn't end in a semicolon, attempt to join it with the following line. If that causes a syntax error, insert a semicolon and try again".

In other words, ASI only happens when a semicolon is required, not when one is possible.

Your hypothesis would be true if not this case:

return { a:1 };

Why it injects ';' after the return? This

return { a:1 };

is perfectly valid construction.

BTW: what about "use strict" ? Does it govern ASI parsing rules? If not then why?

No, strict mode has no effect on ASI.

Too bad IMO.

-- Andrew Fedoniouk.

terrainformatica.com

# Allen Wirfs-Brock (12 years ago)

On Jul 12, 2013, at 5:09 PM, Andrew Fedoniouk wrote:

Your hypothesis would be true if not this case:

return { a:1 };

Why it injects ';' after the return? This

Because, the actual ECMAScript grammar says a new line can't occur between the 'return' keyword and the optional return expression. www.ecma-international.org/ecma-262/5.1/#sec-12.9

You really need to read and understand the relevant portions of the specification before engaging in this sort of discussion. www.ecma-international.org/ecma-262/5.1/#sec-7.9

# Rick Waldron (12 years ago)

On Fri, Jul 12, 2013 at 8:09 PM, Andrew Fedoniouk <news at terrainformatica.com

wrote:

On Fri, Jul 12, 2013 at 4:17 PM, Tab Atkins Jr. <jackalmage at gmail.com> wrote:

On Fri, Jul 12, 2013 at 3:55 PM, Andrew Fedoniouk <news at terrainformatica.com> wrote:

Seems like I am not getting that famous ASI thing.

I do not understand why here:

foo (exp);

there is no semicolon injected. It rather should be this:

foo; (exp);

if that ASI thing has any traces of logic behind.

It has a very simple logic, just not the one you're assuming.

You're probably thinking that the rule is "if a line doesn't end in a semicolon, and you can insert one without causing this line or the next to have a parse error, do so". The actual rule is "if a line doesn't end in a semicolon, attempt to join it with the following line. If that causes a syntax error, insert a semicolon and try again".

In other words, ASI only happens when a semicolon is required, not when one is possible.

Your hypothesis would be true if not this case:

Tab's response is not a hypothesis, it's a generalization of the specification. ASI rules are defined, unambiguously, in the ECMAScript spec.

return { a:1 };

Why it injects ';' after the return? This

return { a:1 };

is perfectly valid construction.

For the same reason I've mentioned several times in this thread: a [no LineTerminator here], which exists between return and the optional Expression:

return [no LineTerminator here] Expression (optional);

# Andrew Fedoniouk (12 years ago)

On Sat, Jul 13, 2013 at 11:16 AM, Rick Waldron <waldron.rick at gmail.com> wrote:

On Fri, Jul 12, 2013 at 8:09 PM, Andrew Fedoniouk <news at terrainformatica.com> wrote:

On Fri, Jul 12, 2013 at 4:17 PM, Tab Atkins Jr. <jackalmage at gmail.com> wrote: ....

In other words, ASI only happens when a semicolon is required, not when one is possible.

Your hypothesis would be true if not this case:

Tab's response is not a hypothesis, it's a generalization of the specification. ASI rules are defined, unambiguously, in the ECMAScript spec.

return { a:1 };

Why it injects ';' after the return? This

return { a:1 };

is perfectly valid construction.

For the same reason I've mentioned several times in this thread: a [no LineTerminator here], which exists between return and the optional Expression:

return [no LineTerminator here] Expression (optional);

OK, what about this definition then

[name] [no LineTerminator here] [literal object declaration];

So this:

foo.bar { one:1, two:1 };

will be parse as a method call: foo.bar {one:1,two:1};

And this:

foo.bar { one:1, two:1 };

as in case of 'return' will be parsed with ASI in effect:

foo.bar; { one:1, two:1, };

Any other discussions about attempts to transform Java/C/C++/D grammar (where '\n' is just a space) to FORTRAN/BASIC-derived line oriented grammars (Python/Ruby/Lua/etc) I'll left for off-list conversations.

# Till Schneidereit (12 years ago)

On Sat, Jul 13, 2013 at 10:49 PM, Andrew Fedoniouk < news at terrainformatica.com> wrote:

OK, what about this definition then

[name] [no LineTerminator here] [literal object declaration];

So this:

foo.bar { one:1, two:1 };

will be parse as a method call: foo.bar {one:1,two:1};

And this:

foo.bar { one:1, two:1 };

as in case of 'return' will be parsed with ASI in effect:

foo.bar; { one:1, two:1, };

This is very different to the return case in terms of cognitive load. Associating return with "ah, no ASI here" is much simpler than having to be on the lookup for ASI exceptions almost everywhere.

Also, return at least returns, no matter what. Your proposal would make the newline relevant in an entirely new way: by making it the difference between having a statement (the foo.bar) that's effect-less in most cases (i.e., if foo.bar isn't an effect-ful getter) followed by a block, and a function call with the block as an argument.

Any other discussions about attempts to transform Java/C/C++/D grammar

(where '\n' is just a space) to FORTRAN/BASIC-derived line oriented grammars (Python/Ruby/Lua/etc) I'll left for off-list conversations.

Nobody's attempting to do anything of the sort - the ASI rules have been unchanged for a long time.