Parameter lists as arguments destructuring sugar

# Sean Eagan (12 years ago)

I think it could be quite useful to spec parameter lists as mere arguments destructuring sugar, something like this...

function f (this = defaultThis|a, , c = "c", ...d) { //... }

...could desugar to...

function f () { const this = this ?? defaultThis; var [a, , c = "c", ...d] = arguments; //... }

Notes:

  • |arguments| is an inaccessible construct (as seen in many other desugaring examples) similar to |arguments|. Didn't use |arguments| since sharp (and eventually all) functions want to deprecate it.

  • "this = defaultThis|" is a stand in for whatever is decided as to the discussion in [1].

  • |this| represents any explicit this binding via call, apply, bind, softBind etc. or undefined if there is no explicit binding;

  • |??| is the default operator [2]. I think at least in strict mode this is not quite correct since it is possible to explicitly set |this| to |undefined|.

  • Parameter lists would still be used to set a function's internal [[FormalParameters]] property, which might be avoidable however for sharp functions since they do not have |arguments|

This could simplify the spec in regard to parameter lists, and would remove the need to normatively spec rest parameters altogether. More importantly, it would bring feature parity between parameter lists and array destructuring, thus allowing for:

  • Default values in array destructuring patterns

    [x, y, z = "z"] = arr;

  • Omission of identifiers for non-important parameters in callback functions

    function callback (x, , z) { return foo(x, z); }

Also, just regarding parameter lists and destructuring in general, here are some additional potentially useful features:

  • Don't require parameters with default values to occur at the end of the parameter lists since all parameters already have an implicit default value of |undefined|

    function(x = 5, y){}

    ... could be considered equivalent to...

    function(x = 5, y = undefined){}

  • Overriding var, let, const bindings (is this already allowed? couldn't tell from [3])

    let [a, const b, var {x, let "y": z}] = arr;

  • Early spread operator, see [4].

[1] esdiscuss/2011-March/013437 [2] strawman:default_operator [3] harmony:destructuring [4] esdiscuss/2011-April/013528

# Sean Eagan (12 years ago)

Also, as destructuring / rest parameters / "this = defaultThis|" already allows things like...

function (this = defaultThis| [a, {b}], ...c ){}

... it would probably be useful to allow equivalent functions to be declared dynamically with "new Function", something like ...

let f = new Function( "this = defaultThis| [a, {b}], ...c", "" );

As mentioned above, the main opportunity here is feature parity between parameter lists and array destructuring, the sugar part would just be a bonus. Just to improve the clarity of the desugaring though, here are some improvements...

  • don't desguar to "function f() ..." as that would cause infinitely recursive desugaring, instead use the dynamic function declaration extension proposed above
  • to think from a functional POV, |this| could instead just be the first element of |arguments|.
  • |this| is normally unassignable (I couldn't actually find this requirement in the ES5 spec though, probably just overlooking it), so it should really be setting the function execution context's ThisBinding, which could be represented as ThisBinding
  • avoid overriding an explicit |undefined| ThisBinding from ES5 section 10.4.3 step 1 by checking ThisBinding explicitness represented as the boolean ThisBindingIsExplicit

function f (this = defaultThis|a, , c = "c", ...d) { //... }

... would then desugar to ...

let parameterList = "a, , c = "c", ...d"; var f = new Function( parameterList, " var [ThisBinding = (ThisBindingIsExplicit ? ThisBinding : defaultThis), " + parameterList] = arguments; //..." );

# Sean Eagan (12 years ago)

Sorry, scratch the idea of passing ThisBinding as arguments[0]. The "improved" desugaring should have been...

let parameterList = "a, , c = "c", ...d"; var f = new Function( parameterList, "!ThisBindingIsExplicit && ThisBinding ??= defaultThis; var [" + parameterList + "] = arguments; //..." );

I'll be quiet now.

Thanks, Sean Eagan

# Sean Eagan (12 years ago)

Actually, forget about desugaring. Here is a refined set of proposals to replace what's above:

Define parameter lists in terms of array destructuring:

Why:

Parameter lists already perform limited destructuring of function arguments, why not give them full array destructuring power, especially with the eventual deprecation of |arguments|?. Conversely, array destructuring could benefit from default values. Also, the principle of least surprise would suggest that the two should be consistent with each other, which this proposal would guarantee, even if extensions (such as those proposed below) were to be added ?

How:

ES5 spec:

Replace section 10.5 step 4, b-f with...

  b. Call env's DestructureArray concrete method passing args,

names, and strict as the arguments.

...where "DestructureArray" refers to step 3 in the algorithm on

the destructuring page [1].

Wiki:

Merge the parameter default values page [2] into the destructuring page.

Remove the rest parameters page [3], and call out rest parameters

as an example use case on the destructuring page.

Potential destructuring extensions

Some of this depends on the first proposal, 3 is new, 2 (except 2ai) may already be allowed due to how LValue is specified in [1], not sure...

LValue ::= <any lvalue expression allowed in a normal assignment expression>

  1. Allow the optional spread operator element (aka rest parameter) and any default values to occur anywhere in array destructuring patterns (including parameter lists), not just at the end.
a) spread operator element does not start getting filled up until

all other elements are filled up, including those after it b) make identifier for spread operator element omittable, as is true for any other element

function(important, ..., alsoImportant) {}
[important, ..., alsoImportant] = arr;

c) if an element does not have an explicit default value, then it

has an implicit |undefined| default value

function(x = 5, y){}
[x = 5, y] = arr;
  1. Allow var, let, const, and any future modifiers inside destructuring patterns, overriding any outside modifiers
let [a, const b, var {x, let "y": z}] = arr;

a) This would also allow for alternate modifiers for parameters

  function(const x, const y) {}

  i ) Maybe sharp function [4] parameters should be let scoped by

default rather than var?

    #(x, y) {/*x and y are let scoped here*/}
  1. Allow default values in object destructuring
{first = 'unknown', last = 'unknown'} = name;
{first: firstName = 'unknown', last: lastName = 'unknown'} = name;

Changes from ES5 needed for parameter lists

In section 10.6, add logic to map between rest parameter elements to |arguments| elements, and to ignore omitted parameters.

Update section 15.3.2.1, allow for arbitray parameter list to be used, possibly just let the first parameter be the entire parameter list...

let f = new Function( "this = defaultThis| [a, {b}], ...c", "//..." );

[1] harmony:destructuring [2] harmony:parameter_default_values [3] harmony:rest_parameters [4] strawman:shorter_function_syntax

Thanks, Sean Eagan

# Bob Nystrom (12 years ago)

I've implemented an unrelated language that works this way, and it is really nice, but I don't know if moving ES to that after the fact would work. But maybe I'm misreading your proposal. Consider:

function show(a, b, c) { log(a + " | " + b + " | " + c); } var arg = [1, 2, 3]; show(arg);

Currently, that prints "1, 2, 3 | undefined | undefined".

With your proposal, would that destructure arg on entry and print "1 | 2 | 3"? Or is the idea that a function call implicitly array-ifies its argument list to avoid that, so "show(arg)" implicitly is semantically "show([arg])" and you get the original behavior?

Assuming the above is resolvable, would you also be able to use object destructuring in a function parameter list? I.e.:

function show(x: x, y: y) { log(x + ", " + y); } var point = { x: 1, y: 2 }; show(point); // "1, 2"

# Sean Eagan (12 years ago)

This stuff should all be backward compatible. You wouldn't get "1 | 2 | 3" unless you did something like "show.apply(null, arg)" or "show(...arg)".

I considered object structuring/destructuring of arguments, but additional syntax would be needed on parameter lists and function invocations since the shortcut form of object structuring / destructuring i.e. "{x, y}", would translate to "f(x, y)" and "function (x, y){}" which are indistinguishable from traditional forms. Also, if object destructuring of function arguments were supported (they would be with this proposal, the destructuring page has an example of it, but it's not fully specified), then you could just do this...

function ({a, b}){} //or function ({a: b, c: d}){}

Thanks, Sean Eagan

# Brendan Eich (12 years ago)

On Apr 9, 2011, at 10:08 PM, Sean Eagan wrote:

function ({a, b}){} //or function ({a: b, c: d}){}

This has been implemented since at least Firefox 3 (also IINM supported in Rhino):

js> function f({a, b}){print(arguments[0], arguments.length, a, b)}

js> f({a:42, b:99}) [object Object] 1 42 99 js> function g({a:x, b:y}){print(arguments[0], arguments.length, x, y)}

js> g({a:43, b:100}) [object Object] 1 43 100

It is part of the Harmonious Proposal.

The actual (positional) parameters reflect as arguments properties, while the formal (destructured-to) variables are local bindings equivalent to var declarations, initialized before the body of the function is evaluated.