Clarification on function default param values

# Jeff Morrison (12 years ago)

So I think during the last meeting it was decided that we'd now have two scopes for functions with default param values: One for head/params, and one for the function body. Was it also agreed that we'd use let-binding (vs var-binding) for the default-value expressions?

I also just wanted to clarify some of the awkward edge-case scenarios as I understand them to be sure we're all on the same page:

var x=1, y=2;
function foo(x=3, y=y, z=y) {
   return [x, y, z];
}
foo(); // [3, undefined, undefined] ? or [3, 2, 2] ?
foo(2); // [2, undefined, undefined];
foo(2, 3); // [2, u];
x; // 1
y; // 2


var x = 1, y=2;
function foo(x=3,y=x+1) {
   return [x, y];
}
foo(); // [3, 4];
foo(2); // [2, 4];
foo(2, 3); // [2, 3];
x; // 1
y; // 2


var x = 1, y=2;
function foo(x=3, y=(x=undefined,4)) {
   return [x,y];
}
foo(); // [undefined, 4]
foo(2); // [undefined, 4]
foo(2, 3); // [2, 3] ? or [undefined, 3] ?
x; // 1
y; // 2


function foo(bar=(function() { return 'param-inner'; })) {
   var ret = bar();
   function bar() {
     return 'body-inner';
   }
   return ret;
}
foo(); // 'body-inner'
foo(function() { return 'futility'; }); // 'body-inner'
# Jeff Morrison (12 years ago)

Typo in first scenario fixed

# Dmitry Soshnikov (12 years ago)

On Mon, Sep 30, 2013 at 1:17 PM, Jeff Morrison <lbljeffmo at gmail.com> wrote:

So I think during the last meeting it was decided that we'd now have two scopes for functions with default param values: One for head/params, and one for the function body.

Just re-read meeting notes, OK, cool on two scopes (seems like they are not in the spec yet). Want to double-check though: whether it will be possible to transpile now to ES3? From what I've seen in the notes, the head-scope will be able to access this value of the activation, etc. Not sure how to transpile now. Could someone give an example of a wrapper scope please, and confirm whether it's transpilable now?

I also just wanted to clarify some of the awkward edge-case scenarios as I understand them to be sure we're all on the same page:

Good examples, but in order to answer I need to see the exact structure of the head-scope. Because of this value I'm afraid it won't be static-time transformable or something.

# Matthew Robb (12 years ago)
var f = function(a=this){}

would transpile to something like:

var f = (function(){

function __funcHead__(){
a=this;
}
function __funcBody__(){
// do stuff
}

var a;

return function(a){__funcHead__.apply(this, arguments);return
__funcBody__.call(this)}

}());

If my interpretation is correct.

# Brandon Benvie (12 years ago)

Not exactly. We're talking about lexical scopes, not function scopes. this applies at the function scope (aside from global and arrowfunc scope).

# Brandon Benvie (12 years ago)

On 9/30/2013 3:51 PM, Dmitry Soshnikov wrote:

Just re-read meeting notes, OK, cool on two scopes (seems like they are not in the spec yet). Want to double-check though: whether it will be possible to transpile now to ES3? From what I've seen in the notes, the head-scope will be able to access this value of the activation, etc. Not sure how to transpile now. Could someone give an example of a wrapper scope please, and confirm whether it's transpilable now?

I believe it is transpilable if you use an inner function scope. Someone correct me if my translations are semantically incorrect:

ES6:

var x = 1, y = 2;
function foo(x = 3, y = y, z = y) {
   return [x, y, z];
}

ES3:

var x = 1, y = 2;
function foo(x, y, z) {
   // defaults
   if (x === undefined) {
     x = 3;
   }
   if (y === undefined) {
     y = y; // essentially a no-op
   }
   if (z === undefined) {
     z = y;
   }

   // body
   return (function(x, y, z){
     return [x, y, z];
   }).call(this, x, y, z);
}

ES6:

var x = 1, y=2;
function foo(x=3, y= (x = undefined, 4)) {
   return [x,y];
}

ES3:

var x = 1, y=2;
function foo(x, y) {
   // defaults
   if (x === undefined) {
     x = 3;
   }
   if (y === undefined) {
     y = (x = undefined, 4);
   }

   // body
   return (function(x, y) {
     return [x, y];
   }).call(this, x, y);
}

ES6:

function foo(bar = function() { return 'param-inner' }) {
   var ret = bar();
   function bar() {
     return 'body-inner';
   }
   return ret;
}

ES3:

function foo(bar) {
   // defaults
   if (bar === undefined) {
     bar = function() { return 'param-inner'; };
   }

   // body
   return (function(bar){
     var ret = bar();
     function bar() {
       return 'body-inner';
     }
     return ret;
   }).call(this, bar);
}

A clearer example of my own which I think demonstrates the scoping better (or exposes a problem with my understanding):

ES6

function foo(x, y = function() { return x }) {
   x++;
   return x + y();
}

foo(1); // is this 3 or 4?

ES3:

function foo(x, y) {
   // defaults
   if (y === undefined) {
     y = function() { return x };
   }

   // body
   return (function(x, y) {
     x++;
     return x + y();
   }).call(this, x, y);
}

foo(1); // 3
# Brandon Benvie (12 years ago)

I'm actually now really curious what the following does:

function foo(x, y = (() => { arguments[0] = "foo"; return "bar" })()) {
   return [x, y];
}

foo(5);

Arrow functions are not implicitly strict currently, right? If so, the above should return ["foo", "bar"].

# Brendan Eich (12 years ago)

Brandon Benvie <mailto:bbenvie at mozilla.com> September 30, 2013 8:48 PM I'm actually now really curious what the following does:

function foo(x, y = (() => { arguments[0] = "foo"; return "bar" })()) {
  return [x, y];
}

Easy: arguments is an early error in the body of an arrow.

harmony:arrow_function_syntax

# Brandon Benvie (12 years ago)

On 9/30/2013 8:57 PM, Brendan Eich wrote:

Easy: arguments is an early error in the body of an arrow.

Ok cool, that simplifies my previous examples a bit then. Instead of using call and specifying the name of each argument, you can just use .apply(this, arguments).

# André Bargull (12 years ago)

Easy: arguments is an early error in the body of an arrow.

harmony:arrow_function_syntax

This restriction is not specified in the rev19 draft. Currently arrow functions don't have an own arguments binding, but instead access the outer functions arguments object, similar to this. Disallowing the identifier "arguments" in PrimaryExpressions also should imply disallowing "arguments" as a BindingIdentifier, which shifts arrow functions again into "almost strict-mode".

# Andreas Rossberg (12 years ago)

On 30 September 2013 22:17, Jeff Morrison <lbljeffmo at gmail.com> wrote:

So I think during the last meeting it was decided that we'd now have two scopes for functions with default param values: One for head/params, and one for the function body. Was it also agreed that we'd use let-binding (vs var-binding) for the default-value expressions?

I'm not entirely sure, actually. We discussed some pretty obscene examples that would arise form doing a var-like thing, while nobody came up with an example where it would be useful. I think in the end the tendency was towards let-like, although I don't remember a formal consensus. I assume let-like in my replies below.

I also just wanted to clarify some of the awkward edge-case scenarios as I understand them to be sure we're all on the same page:

var x=1, y=2; function foo(x=3, y=y, z=y) { return [x, y, z]; }

All parameters are in scope in the default expressions, so y is never going to resolve to the global var binding. Also, all defaults are being evaluated and bound left to right.

For more examples, see also my slides, which weren't yet linked in the meeting notes that Rick posted:

rwaldron/tc39-notes/blob/master/es6/2013-09/default-arguments.pdf

foo(); // [3, undefined, undefined] ? or [3, 2, 2] ? foo(2); // [2, undefined, undefined];

These two would throw, because y is used before being bound (just like 'let y=y' throws).

foo(2, 3); // [2, undefined, undefined];

This one would be [2, 3, 3].

x; // 1 y; // 2

var x = 1, y=2; function foo(x=3,y=x+1) { return [x, y]; } foo(); // [3, 4]; foo(2); // [2, 4];

No, [2, 3] for the latter.

foo(2, 3); // [2, 3]; x; // 1 y; // 2

var x = 1, y=2; function foo(x=3, y=(x=undefined,4)) { return [x,y]; }

This one I really dislike. I had actually proposed to disallow assignment to other parameters in default expressions, but that got a lot of resistance.

foo(); // [undefined, 4] foo(2); // [undefined, 4]

Yes.

foo(2, 3); // [2, 3] ? or [undefined, 3] ?

[2, 3], the default expressions are not evaluated if the argument isn't undefined.

x; // 1 y; // 2

function foo(bar=(function() { return 'param-inner'; })) { var ret = bar(); function bar() { return 'body-inner'; } return ret; } foo(); // 'body-inner' foo(function() { return 'futility'; }); // 'body-inner'

Yes.

# Andrea Giammarchi (12 years ago)

Just a quick one. I think the best representation in ES3 or 5 would be the following

function foo(x, y, z) {
  switch(arguments.length) {
    case 0: x = 1;
    case 1: y = 2;
    case 2: z = 3;
  }
  // whatever logic involved, i.e.
  return x + y + z;
}

no break and no default is meant.

# Brendan Eich (12 years ago)

No, we are using undefined actual parameter value, not argument.length, to trigger defaulting -- as discussed many times and cited by me (again) recently in reply to Oliver. Please do not go backward.

# Allen Wirfs-Brock (12 years ago)

On Oct 1, 2013, at 12:03 AM, André Bargull wrote:

This restriction is not specified in the rev19 draft. Currently arrow functions don't have an own arguments binding, but instead access the outer functions arguments object, similar to this. Disallowing the identifier "arguments" in PrimaryExpressions also should imply disallowing "arguments" as a BindingIdentifier, which shifts arrow functions again into "almost strict-mode".

The restriction we want and how to enforce it is not all that clear.

We don't really want to have an "almost strict" mode for the body of arrow functions, if that was going to be the case I think it would be much better to simply say that arrow functions are always strict.

The spec. currently applies the static semantics of strict mode formal parameters (no duplicate names, can't name a parameter 'eval' or 'arguments') to arrow function formal parameters, but in all other ways both the formal parameter list (think default value expressions) and the arrow function body follow the strictness of the surrounding code.

that means things like the following are valid in non-strict arrow functions:

() =>  {function arguments() {}; return arguments}
() => {let arguments =5; ...}

So we can't just statically disallow 'arguments' references in a ConciseBodu

Also, we have to have consistent behavior for an

eval('arguments')

that appears in a ConciseBody.

Here are possible reasonable alternatives for handling arguments in arrow functions:

  1. nothing special, same rules as a FunctionExpression. An arguments object is available and strict/non-strict distinctions apply both statically and dynamically.

  2. nothing special, but strict arguments object. Just like #1 except it always has a strict mode arguments object (no joining of augments elements and formal parameters

  3. #2, plus strict mode parameter naming restrictions are also applied (no duplicates, can't use 'eval' or 'arguments' as parameter names)

  4. no-arguments objects with shadowing, 'arguments' binds to undefined. Arrow functions do not have an arguments objects but they have an implicit constant binding of 'arguments' in their parameter scope whose value is undefined. References to 'arguments' in the body evaluate to undefined. (unless, non-strict and there are explicit declarations of the name 'arguments');

  5. no-arguments objects with shadowing, 'arguments' is in temporal dead zone. Arrow functions do not have an arguments objects but they have an implicit binding of 'arguments' in their parameter scope that is never marked as initialized. References to 'arguments' in the body throw because they are temporal dead zone references. (unless, non-strict and there are explicit declarations of the name 'arguments' that shadow the parameter level binding);

  6. nothing special, but no arguments object, normal lexical scoping. Like #1 except that there is no arguments object or local binding of 'arguments'. References to arguments resolve via the enclosing scope.

  7. strict mode name restrictions, but no arguments object, normal lexical scoping. Like #3 except that there is no arguments object or local binding of 'arguments'. References to arguments resolve via the enclosing scope.

The rev19 spec. current has approximately #7 but there isn't anything final about that. It sounds to me like some think either #3 or #4 is the plan of record although I don't think we've talked about it at a TC39 meeting that this level of detail.

I think #3 would be a good solution. So is #7.

#3 probably would seem reasonable to JS programmers. #7 probably is what lexical scoping heads would expect. #4 or #5 is a completely new semantics that I don't think anyone expects.

# Andrea Giammarchi (12 years ago)

I see. Does null count as undefined too or it must be explicitly undefined ? not that using a transpiler this matters much ... just curious about how the ES < 6 will look like.

# Erik Arvidsson (12 years ago)

3 and 7 both seems good. Personally I prefer #7 but I'm worried that it might be too surprising that arguments refers to the outer function.

# Brendan Eich (12 years ago)

Andrea Giammarchi wrote:

Does null count as undefined too

No. See the draft spec and many threads and meeting notes.

# Mark S. Miller (12 years ago)

I agree that the discussions as I remember them do not go into this level of detail. Thanks for being so explicit about precise choices.

However, I do recall that the consensus we had was enough to rule out #3, or indeed any solution where arrow functions have their own arguments object. And even if I recall wrongly, I do think arrow functions should not get their own arguments object -- that's too surprising from an arrow-function-as-smalltalk-block-like perspective.

.#7 can be explained in a memorable way:

Generally variables are brought into scope by an explicitly appearing defining occurrence. Two exceptions are the "function" brings into scope both "this" and "arguments". These remain in scope until shadowed by a nested "function" or by an explicit definition. Note that "this" can never be explicitly defined, and "arguments" can only be explicitly defined in non-strict code.

As of ES6, a variety of other function-defining constructs, like "function", implicitly bring into scope a new "this" and "argument". Arrow-functions are not one of these. Within an arrow function, both "this" and "arguments" are lexically captured from the enclosing context.

# Andrea Giammarchi (12 years ago)

I need to read everything Brendan suggested but if anyone would be so kind to refresh my memories on this arrow function I'd appreciate that. I don't need much more than yes/no as answer, thanks.

  1. var o = {method: () => this}; will o.method() return o ? (I guess

nope)

Considering the following code:

var Proto = (function(){
  this.method = () => this;
  return this.constructor;
}.bind(function Proto() {}.prototype));
  1. will it work as expected per each new Proto object ? (I guess nope, everything referred to Proto.prototype)

Considering the following code:

var o = {i: 0};

(function(O){

  O.fatMethod = (i) => i + this.i;
  O.justMethod = function (i) {
    return i + O.i;
  };

}.bind(o,O));
  1. will the fatMethod be ideally/theoretically/technically faster to execute once made hot through JIT capable engines (or made preventively hot since immutable in such form) ?

My bonus, humble, honest, and final question would be:

  1. what kind of problem is this fat arrow feature trying to solve exactly, if it cannot be used for classes, direct methods assignment, but only for some runtime event assignment instead of using bind and still being unable to remove that listener later on ?

Thanks a lot in advance for any refreshing and/or enlightening answer, it's actually a while I am wondering this stuff.

Best

# Dmitry Soshnikov (12 years ago)

On Mon, Sep 30, 2013 at 8:21 PM, Brandon Benvie <bbenvie at mozilla.com> wrote:

On 9/30/2013 3:51 PM, Dmitry Soshnikov wrote:

Just re-read meeting notes, OK, cool on two scopes (seems like they are not in the spec yet). Want to double-check though: whether it will be possible to transpile now to ES3? From what I've seen in the notes, the head-scope will be able to access this value of the activation, etc. Not sure how to transpile now. Could someone give an example of a wrapper scope please, and confirm whether it's transpilable now?

I believe it is transpilable if you use an inner function scope. Someone correct me if my translations are semantically incorrect:

ES6:

var x = 1, y = 2;
function foo(x = 3, y = y, z = y) {
  return [x, y, z];
}

ES3:

var x = 1, y = 2;
function foo(x, y, z) {
  // defaults
  if (x === undefined) {
    x = 3;
  }
  if (y === undefined) {
    y = y; // essentially a no-op
  }
  if (z === undefined) {
    z = y;
  }

  // body
  return (function(x, y, z){
    return [x, y, z];
  }).call(this, x, y, z);
}

OK. Thanks for the example. There are some edge cases with transpiling to ES3, e.g. if the inner function mutates the arguments.callee by attaching a new property to it, or something (the property is attached to the inner function then, not to the outer wrapper). But transpiling cases shouldn't stop the actual spec for engines of course.

Also, what about using other parameter transforms in conjunction, e.g. rest parameters and destructuring: are they duplicated in the signature of the inner function, is their coded are injected into the inner function or to the outer one?

A clearer example of my own which I think demonstrates the scoping better (or exposes a problem with my understanding):

ES6

function foo(x, y = function() { return x }) {
  x++;
  return x + y();
}

foo(1); // is this 3 or 4?

ES3:

function foo(x, y) {
  // defaults
  if (y === undefined) {
    y = function() { return x };
  }

  // body
  return (function(x, y) {
    x++;
    return x + y();
  }).call(this, x, y);
}

foo(1); // 3

Yeah, this one is debatable though. I think it's either, or. Both variants can be correct, depending on how it's speced. If it's spected that the defaults are evaluated in the function scope, then it shouldn't be a surprise that the if can be 4 (and in this case we probably don't need two scopes. Wondering, can all the edge cases be covered by the single sentence in the spec: "All default values are evaluated in the scope of a function"? Or those edge cases are too not-obvious from the programmers perspectives?).

Dmitry

# Claus Reinke (12 years ago)

Generally variables are brought into scope by an explicitly appearing defining occurrence. Two exceptions are the "function" brings into scope both "this" and "arguments". These remain in scope until shadowed by a nested "function" or by an explicit definition. Note that "this" can never be explicitly defined, and "arguments" can only be explicitly defined in non-strict code.

As of ES6, a variety of other function-defining constructs, like "function", implicitly bring into scope a new "this" and "argument". Arrow-functions are not one of these. Within an arrow function, both "this" and "arguments" are lexically captured from the enclosing context.

Also "super" (implicitly bound in "function", available lexically scoped in arrow function body).

Still following the rule: arrow functions have no implicit bindings and do not interfere with lexical scope (other than adding explicit bindings for their parameters).

Claus

# Brendan Eich (12 years ago)

Thread-hijacking is poor form.

# Andrea Giammarchi (12 years ago)

oops, it wasn't intentional. I should have checked ... opened a new one.

br

# Mark Miller (12 years ago)

On Tue, Oct 1, 2013 at 3:16 PM, Claus Reinke <claus.reinke at talk21.com>wrote:

Generally variables are brought into scope by an explicitly appearing

defining occurrence. Two exceptions are the "function" brings into scope both "this" and "arguments". These remain in scope until shadowed by a nested "function" or by an explicit definition. Note that "this" can never be explicitly defined, and "arguments" can only be explicitly defined in non-strict code.

As of ES6, a variety of other function-defining constructs, like "function", implicitly bring into scope a new "this" and "argument". Arrow-functions are not one of these. Within an arrow function, both "this" and "arguments" are lexically captured from the enclosing context.

Also "super" (implicitly bound in "function", available lexically scoped in arrow function body). Still following the rule: arrow functions have no implicit bindings and do not interfere with lexical scope (other than adding explicit bindings for their parameters).

Excellent point!