Function.length and Default Parameters
ExpectedArgumentCount is used to the the length property of function objects.
We've discussed this extensively before and there doesn't seem to be many plausible use cases for the function length property.
Given that length isn't very useful alignment with the conventions used for the built-ins seems very reasonable.
BTW, some ES<=5.1 built-ins do not perfectly follow this convention. I've suggested that we change the specified length values for such functions to match the ES6 conventions. Probably a safe breaking change.
We've discussed this extensively before and there doesn't seem to be many plausible use cases for the function length property.
Here's the only use case that I've encountered (admittedly not particularly strong): Overriding the behavior of a function/method based on the "signature" of an input function:
function withCallback(callback) {
// Do something which generates an error...
var error = new Error("abc");
// Use callback.length to override behavior...
if (callback.length >= 2)
callback(null, error);
else
throw error;
}
// Throws error
withCallback(val => { ... });
// Logs error
withCallback((val, err) => { if (err) console.log(err); });
// Throws, even though it has the err parameter
withCallback((val = 123, err) => { ... });
Not great, but...
Another thought I had was that someone might want to use defaults to replace undefined actual parameters with null values:
function f(a = null, b = null) { ... }
f(void 0); // normalized to null
f.length = 0; // Intentional?
Again, not great, but...
On Wed, Oct 10, 2012 at 1:22 PM, Kevin Smith <khs4473 at gmail.com> wrote:
We've discussed this extensively before and there doesn't seem to be many
plausible use cases for the function length property.
Here's the only use case that I've encountered (admittedly not particularly strong): Overriding the behavior of a function/method based on the "signature" of an input function:
express for node already does this, for error handlers:
http://expressjs.com/guide.html#error-handling
express is very popular; #4 on Most Depended Upon packages; #1 on Most Starred at npm:
I will admit a certain amount of dislike for this technique, and
overloading in general; would it really be so hard to provide an
app.useError()
API instead of 'overloading' app.use()
?
express for node already does this, for error handlers:
http://expressjs.com/guide.html#error-handling
express is very popular; #4 on Most Depended Upon packages; #1 on Most Starred at npm:
This is helpful. So, judgement aside, we can say there is a certain level of usage of Function.length out there for overloading based on a function's "signature". It seems to me that reporting Function.length as the number of formal parameters, minus rest, will be more appropriate for this use case.
Kevin Smith wrote:
express for node already does this, for error handlers: http://expressjs.com/guide.html#error-handling express is very popular; #4 on Most Depended Upon packages; #1 on Most Starred at npm:
This is helpful. So, judgement aside, we can say there is a certain level of usage of Function.length out there for overloading based on a function's "signature". It seems to me that reporting Function.length as the number of formal parameters, minus rest, will be more appropriate for this use case.
And specifically not stopping counting at the first parameter with a default value, right?
On Thursday, October 11, 2012 at 7:36 PM, Kevin Smith wrote:
express for node already does this, for error handlers:
http://expressjs.com/guide.html#error-handling
express is very popular; #4 on Most Depended Upon packages; #1 on Most Starred at npm:
This is helpful. So, judgement aside, we can say there is a certain level of usage of Function.length out there for overloading based on a function's "signature". It seems to me that reporting Function.length as the number of formal parameters, minus rest, will be more appropriate for this use case.
Having trouble finding the resolution, but I'm fairly certain this was discussed and your suggestion here matches consensus
And specifically not stopping counting at the first parameter with a default value, right?
Yep. Would it also work for this use case to stop counting at the first parameter with no default value, after which there are only defaults?
function f(a, b = 2, c, d = 4, ...rest) {}
f.length === 3;
Probably? Not sure...
On Thursday, October 11, 2012 at 11:25 PM, Kevin Smith wrote:
And specifically not stopping counting at the first parameter with a default value, right?
Yep. Would it also work for this use case to stop counting at the first parameter with no default value, after which there are only defaults?
function f(a, b = 2, c, d = 4, ...rest) {} f.length === 3;
Probably? Not sure...
Neither am I...
This looks like the sort of thing that will get labeled "uselessly unreliable", then again I was in favor of making length equal the number of formal parameters—rest included.
This looks like the sort of thing that will get labeled "uselessly unreliable", then again I was in favor of making length equal the number of formal parameters—rest included.
Right - the simpler the rule, the better for this kind of thing. I wouldn't include rest, though, because it's "cardinality" is really infinity, and therefore not particularly useful in this context.
But the simplest rule would require a change to the reported length of some built-ins, as Allen has said. Tradeoffs...
On Friday, October 12, 2012 at 9:18 AM, Kevin Smith wrote:
This looks like the sort of thing that will get labeled "uselessly unreliable", then again I was in favor of making length equal the number of formal parameters—rest included.
Right - the simpler the rule, the better for this kind of thing. I wouldn't include rest, though, because it's "cardinality" is really infinity, and therefore not particularly useful in this context. +1
Sorry, should've clarified: that was my initial inclination, some time ago. Yes I agree with "everything but the rest".
On Fri, Oct 12, 2012 at 9:23 AM, Rick Waldron <waldron.rick at gmail.com> wrote:
Right - the simpler the rule, the better for this kind of thing.
Anything but optional and rest (as speced) is the simplest rule I can think of, except everything including rest, but no one wants that.
The problem is that length is not useful for reflection anyway. The right way forward is to provide a better reflection API that exposes all the relevant information about the parameters.
Anything but optional and rest (as speced) is the simplest rule I can think of, except everything including rest, but no one wants that.
But the current spec does not say that. It says stop counting at the first default, which doesn't really tell anyone anything useful about the function's signature.
function f(a = 1, b, c, d, e, f) {}
f.length === 0; // Huh?
Unless I'm misreading...?
"Simple" may be in the eye of the beholder, but I contend that the above is surprising.
The problem is that length is not useful for reflection anyway. The
right way forward is to provide a better reflection API that exposes all the relevant information about the parameters.
A better reflection API sounds good, but your statement that length is not useful is demonstrably false. API judgements aside, we know that it is currently used with non-WTF results.
On Oct 11, 2012, at 8:11 PM, Rick Waldron wrote:
On Thursday, October 11, 2012 at 7:36 PM, Kevin Smith wrote:
express for node already does this, for error handlers:
http://expressjs.com/guide.html#error-handling
express is very popular; #4 on Most Depended Upon packages; #1 on Most Starred at npm:
This is helpful. So, judgement aside, we can say there is a certain level of usage of Function.length out there for overloading based on a function's "signature". It seems to me that reporting Function.length as the number of formal parameters, minus rest, will be more appropriate for this use case.
Having trouble finding the resolution, but I'm fairly certain this was discussed and your suggestion here matches consensus
Previous threads where this topic was discussed:
esdiscuss/2011-August/016361, esdiscuss/2011-September/016417
There was no consensus to change the current ES6 draft specification for function length.
On Fri, Oct 12, 2012 at 11:53 AM, Allen Wirfs-Brock <allen at wirfs-brock.com>wrote:
On Oct 11, 2012, at 8:11 PM, Rick Waldron wrote:
On Thursday, October 11, 2012 at 7:36 PM, Kevin Smith wrote:
express for node already does this, for error handlers:
http://expressjs.com/guide.html#error-handling
express is very popular; #4 on Most Depended Upon packages; #1 on Most Starred at npm:
This is helpful. So, judgement aside, we can say there is a certain level of usage of Function.length out there for overloading based on a function's "signature". It seems to me that reporting Function.length as the number of formal parameters, minus rest, will be more appropriate for this use case.
Having trouble finding the resolution, but I'm fairly certain this was discussed and your suggestion here matches consensus
Previous threads where this topic was discussed:
esdiscuss/2011-September/016417
There was no consensus to change the current ES6 draft specification for function length.
Sorry, I didn't mean consensus to change anything—my "fairly certain" above was just wrong as I had forgotten this: "or the first FormalParameter with an Initialiser.".
Apologies for the confusion.
On Oct 11, 2012, at 4:36 PM, Kevin Smith wrote:
express for node already does this, for error handlers:
http://expressjs.com/guide.html#error-handling
express is very popular; #4 on Most Depended Upon packages; #1 on Most Starred at npm:
This is helpful. So, judgement aside, we can say there is a certain level of usage of Function.length out there for overloading based on a function's "signature". It seems to me that reporting Function.length as the number of formal parameters, minus rest, will be more appropriate for this use case.
Have you found the actual usage of length in node libraries to enforce this sort of signature testing? I was poking around and haven't found it. Note that the expressjs.com/guide.html#error-handling only says "must be defined with an arity of 4, that is the signature (err, req, res, next)". It doesn't say that length is use to enforce this. It might be doing a toString on the function or something else.
Regardless, the specification function length in the current ES6 draft would not break such length-based detection. If you write function (err, req,res,next){}.length you will get 4, just like with ES5.1 So this isn't a breaking change. Any new use of new parameter forms would be a violation of the "must" in the above quote from the express documentation.
So, so far, I have really seen anything new in this recent thread that would cause us to change the decision that has already been made.
There was additional discussion of length in the context of the internationalization API, and a decision at the July TC 39 meeting to "Apply ES6 rules to all functions in Internationalization API." [1]
By ES6 rules we meant what Allen had proposed [2] and what's in section 13.1 of the ES6 draft:
"NOTE The ExpectedArgumentCount of a FormalParameterList is the number of FormalParameters to the left of either the rest parameter or the first FormalParameter with an Initialiser. A FormalParameter without an initializer are allowed after the first parameter with an initializer but such parameters are considered to be optional with undefined as their default value."
This means, optional arguments don't count.
[1] esdiscuss/2012-July/024207 [2] esdiscuss/2011-August/016361
Norbert
Have you found the actual usage of length in node libraries to enforce this sort of signature testing? I was poking around and haven't found it. Note that the expressjs.com/guide.html#error-handling only says "must be defined with an arity of 4, that is the signature (err, req, res, next)". It doesn't say that length is use to enforce this. It might be doing a toString on the function or something else.
visionmedia/express/blob/master/lib/router/index.js#L159
Regardless, the specification function length in the current ES6 draft would not break such length-based detection. If you write function (err, req,res,next){}.length you will get 4, just like with ES5.1 So this isn't a breaking change. Any new use of new parameter forms would be a violation of the "must" in the above quote from the express documentation.
Sure, it's not breaking, but that doesn't mean that it makes sense for the use case we're talking about.
Again:
function f(a = 1, b, c) {}
f.length === 0; // Huh?
On what basis does reporting 0 make sense? How does that communicate anything at all about the function?
From: es-discuss-bounces at mozilla.org [es-discuss-bounces at mozilla.org] on behalf of Kevin Smith [khs4473 at gmail.com] Sent: Friday, October 12, 2012 12:30
Again: function f(a = 1, b, c) {} f.length === 0; // Huh?
You seem to be under the mistaken impression that ES6 allows non-defaulted arguments after default ones. This is not the case. See
for a (very) recent discussion of this.
Have you found the actual usage of length in node libraries to enforce this sort of signature testing? I was poking around and haven't found it.
Side question: has tc39 tried to contact any of the research groups that specialize in large-scale repository mining?
Usage questions here are often not answerable with a simple grep, even though there are lots of JS repositories (github, npm, ..). One needs repo access, storage and search resources, and specialized analysis (at least syntax).
Perhaps some research groups would welcome a challenge with real-world impact, either as specific projects or as a wiki page with challenges from which research students in those groups could pick topics to work on.
Usually, those groups mine repo histories, for trends related to software engineering topics, but their setups might work, or might be adaptable to, tc39's JS-in-the-real-world usage questions. Once such a setup is in place, tc39 members might use it directly to inform language design decisions.
Just a thought, Claus
You seem to be under the mistaken impression that ES6 allows non-defaulted arguments after default ones. This is not the case. See
for a (very) recent discussion of this.
I don't think that is true. See Allen's response here:
Also, I see no evidence in the latest draft that defaults after a non-default is a static error. Please correct me if I'm wrong, of course!
Also, I see no evidence in the latest draft that defaults after a non-default is a static error. Please correct me if I'm wrong, of course!
Sorry, meant "non-defaults after a default" in the above. Need...more...coffeee.
On Oct 12, 2012, at 13:03, "Kevin Smith" <khs4473 at gmail.com<mailto:khs4473 at gmail.com>> wrote:
You seem to be under the mistaken impression that ES6 allows non-defaulted arguments after default ones. This is not the case. See
for a (very) recent discussion of this.
I don't think that is true. See Allen's response here:
Oh, wow, thanks for correction. Sorry for the noise everyone X_x. I got the opposite impression from other responses in the thread. Will be sure to update the @esdiscuss Twitter account.
Yes, so given the case Kevin raises of default-ful followed by default-less I agree with Kevin: length should reflect all but rest (if present).
Allen, what built-ins would this break, do you know from checking what built-ins break under the current draft's rule?
String.prototype.localeCompare Number.prototype.toLocaleString Date.prototype.toLocaleString Date.prototype.toLocaleDateString Date.prototype.toLocaleTimeString Intl.Collator Intl.Collator.supportedLocalesOf Intl.NumberFormat Intl.NumberFormat.supportedLocalesOf Intl.DateTimeFormat Intl.DateTimeFormat.supportedLocalesOf the function returned by Intl.DateTimeFormat.prototype.format
...are all specified in the ECMAScript Internationalization API Specification as taking optional arguments, but not counting them towards the value of their length properties.
Norbert
One more question: are these optional parameters the kind that would want to use parameter default values if self-hosted? If so, that would cement the case for the current spec. Kevin's counter-example would be a "it hurts when I do this!" "don't do it!" thing.
Yes. Most of them take locales and options arguments, and those get eagerly filled in with [] and {}, respectively. The last function takes a time value, and defaults to Date.now().
Now, self-hosted functions have powers that others don't. In the SpiderMonkey self-hosting environment, I can already specify per function whether they should be constructors and whether they should have prototype objects. We may be able to add a way to fake length values too.
More important: These functions were specified this way because TC 39 decided that this is the direction for ES6, and even though ES5 style would have been more convenient for us.
Norbert
Norbert Lindenberg wrote:
More important: These functions were specified this way because TC 39 decided that this is the direction for ES6, and even though ES5 style would have been more convenient for us.
I can't tell if you mean that Kevin's proposal would be adverse, helpful, or neutral.
TC39 does change its mind before the spec is finalized; please don't mind that.
For me (v8) it's more painful. As my code is an v8 extension, I don't have access to the v8 internals, so I can't:
"specify whether they should be constructors and whether they should have prototype objects"
Also, getting the length of the function to match 0, 1... is using lots of argumens[0], [1]... I was told this was less efficient than direct access to the parameters - it also uglifies the code.
2012/10/12 Brendan Eich <brendan at mozilla.org>
We made a decision about Function length and default parameters in the past and I don't see a compelling reason to change that decision now.
The plan of record doesn't impact the node use case that Kevin identified as they specify that 4 non-optional parameters are required and we aren't making any changes in the counting of non-optional parameters for length purposes. Passing callback functions with optional parameters in those corresponding argument positions wouldn't make sense because the call backs are always called using a fixed set of arguments. See www.wirfs-brock.com/allen/posts/166 for a discussion of the hazards of mixing callee optional and caller optional function signatures.
Regarding, parameters without default value initializers that follow parameters with default value initializers. The draft ES6 spec. says that such parameters are treated as if they had an initializer of the form "=undefined". Concretely:
function f(a=42,b,c) {};
is exactly equivalent to:
function f(a=42,b=undefined, c=undefined) {};
Note that if a is "optional" then b and c most also be considered "optional" because if a is not present, then b and c can not be present. EG, this is not legal ES:
f( , 43, 44);
This is also something that was discussed on this list in the past, and that discussion lead to the current design.
Regarding built-ins, the main issue that I consider outstanding concerns some of the legacy built-ins. I would like to restate all of the function signatures in chapter 15 using ES formal parameter conventions. However, some of the legacy built-ins have lengths that don't follow the ES6 conventions
For example, Array.prototype.slice has a legacy length of 2 but its signature would be best expressed as:
Array.prototype.slice(start, end=this.length)
which using the ES6 function length rules would have a length of 1.
push has a legacy length of 1 while it signature would be best expressed as:
Array.prototype.push (...items)
which ES6 says has a length of 0.
Among other things, differences between the generated length property values of ES code functions (note this is a non-configurable, non-writable property) and required specified values make it harder to self-host implementations of built-ins using ES code. I would like to change the specified lengths of all the built-ins to match the length that would be generated if their signature was expressed using default value initializers and rest parameters. I believe that we made some adjustments to the length of a few of the built-ins in ES5 (or implementations adjusted their implementations to match the spec) so I suspect that additional changes to the length of a few built-ins for ES6 would have little impact.
Allen Wirfs-Brock wrote:
We made a decision about Function length and default parameters in the past and I don't see a compelling reason to change that decision now.
I'm ok with that, so (IEEE754 nod): +0.
The plan of record doesn't impact the node use case that Kevin identified as they specify that 4 non-optional parameters are required and we aren't making any changes in the counting of non-optional parameters for length purposes. Passing callback functions with optional parameters in those corresponding argument positions wouldn't make sense because the call backs are always called using a fixed set of arguments. See www.wirfs-brock.com/allen/posts/166 for a discussion of the hazards of mixing callee optional and caller optional function signatures.
Good points.
For example, Array.prototype.slice has a legacy length of 2 but its signature would be best expressed as:
Array.prototype.slice(start, end=this.length)
which using the ES6 function length rules would have a length of 1.
A common use of slice is to clone an array-like as a real array: [].slice.call(arguments) is calling with zero actual parameters. So one could argue that slice's signature is actually
Array.prototype.slice(start=0, end=this.length).
But we can farble it somehow to preserve compatibility, or break if anyone really thinks we should try to roll those dice (I don't see much upside).
push has a legacy length of 1 while it signature would be best expressed as:
Array.prototype.push (...items)
which ES6 says has a length of 0.
By far the most common use-case is to push one value, so for legacy's sake, if not clarity's sake:
Array.prototype.push(value, ...rest)
Among other things, differences between the generated length property values of ES code functions (note this is a non-configurable, non-writable property) and required specified values make it harder to self-host implementations of built-ins using ES code. I would like to change the specified lengths of all the built-ins to match the length that would be generated if their signature was expressed using default value initializers and rest parameters. I believe that we made some adjustments to the length of a few of the built-ins in ES5 (or implementations adjusted their implementations to match the spec) so I suspect that additional changes to the length of a few built-ins for ES6 would have little impact.
I'm not sure, but see above for some minor doubts about your preferred signatures, which might dodge some compat-break risk.
I notice that the current draft spec says:
This aligns ExpectedArgumentCount with built-in functions like Array.prototype.splice in specifying (more or less) the minimum number of arguments for the function to work properly.
Leaving aside congruence with built-in functions, it seems like it would be more helpful to know the total number of formals, minus the rest parameter. Is there any other reason to favor the current semantics?