Bait taken: Arguments about arguments
I created distinct Trac tickets for the separable issues being discussed here:
Ticket #428 has been reopened and should be specifically about isArray and whether or not it return true for the arguments object bugs.ecmascript.org/ticket/428
Ticket #447 is about whether or not arguments object/formal parameter sharing continues after a function return bugs.ecmascript.org/ticket/447
Ticket #448 is about whether or not the semantics of arguments objects should vary between normal and strict mode functions bugs.ecmascript.org/ticket/448
From: es-discuss-bounces at mozilla.org [mailto:es-discuss-bounces at mozilla.org] On Behalf Of Mark S. Miller Sent: Thursday, January 08, 2009 10:17 PM To: Brendan Eich Cc: es3.x-discuss at mozilla.org; es-discuss Subject: Bait taken: Arguments about arguments
On Thu, Jan 8, 2009 at 9:30 PM, Brendan Eich <brendan at mozilla.com<mailto:brendan at mozilla.com>> wrote:
The rationale "we dare not break" applies to other cases of making the strict mode migration tax too high. This came up in the wiki just now, and I've commented in-line at
Spin-off thread fodder, change of subject appropriate if you take my bait ;-).
Regarding your inline
(I thought I was clear enough at Kona, and Maciej was perfectly clear in the ticket:
and
(This is not what was agreed to at Kona. We need to talk about this at the next face to face.
We have divergent memories of what was agreed. This is an issue I care about a lot, so I'm confident I didn't simply forget. Probably we misunderstood each other somehow. In any case, what matters now is that we come to agreement.
You write (with bullets turned into numbers):
1) Mandating two arguments implementations is onerous.
2) In no case must eager construction of an Array instance be required, but web compatibility plus strict mode would seem to require it for foo.arguments.
3) The migration tax for strict mode should not be hiked to include "find all the hidden aliasing dependencies in the legacy codebase you inherited".
Re #1: Allen and Crock relayed this concern. I am skeptical, as there's a trivial source-to-source transformation from the language L1 that I would prefer to program in to the language L2 that you would prefer to implement:
If strict function foo in language L1 mentions 'arguments' freely or contains an occurrence of the eval operator (this is statically determinable, and we do not count either 'arguments' or 'eval' in nested functions), for example
function foo(...) {
"use strict";
... arguments ...
}
then translate it to strict function foo in language L2, where t1 represents an variable name not otherwise mentioned in the program, as
function foo(...) {
"use strict";
var t1 = Object.freeze(Array.slice(arguments, 0));
... t1 ...
}
In other words, in exactly these functions, snapshot arguments eagerly into a genuine array, and then replace all further uses of arguments with the variable holding that snapshot. It's not quite a source-to-source translation, since additional magic is required for a free 'arguments' within an eval()ed script to address t1 rather than L2's "arguments", which doesn't exist in L1. Actual implementations can be more efficient than this, but this transform is adequate to demonstrate lack of burden.
Re #2: If foo is a strict function, then foo.arguments throws an error. So the only functions where we need this eager creation are those that either need arguments anyway, or those that directly use the eval operator, both of which are statically visible.
Re #3: Actual use of arguments aliasing is very rare. (Re bait, compare to the frequency of feature testing.) We freeze arguments so most actual uses of such aliasing will result in noisy failures. Probably much current aliasing is more of a source of buggy behavior than a feature used intentionally. It is good if strict mode exposes bugs in ported programs. New programmers should learn the strict language first or only, so we want to remove such landmines from it. Intentional use of aliasing is probably about as common as intentional use of implicit wrapping, and so your #3 would equally argue against the happy bug fix agreement we just had.
Btw, I very clearly remember you agreeing in Kona that we should freeze strict arguments for this reason. I raise this not to start a "But you said. No you said" loop, but in hopes of jogging your memory. Do you remember this?
On Jan 8, 2009, at 10:17 PM, Mark S. Miller wrote:
In other words, in exactly these functions, snapshot arguments
eagerly into a genuine array, and then replace all further uses of
arguments with the variable holding that snapshot. It's not quite a
source-to-source translation, since additional magic is required for
a free 'arguments' within an eval()ed script to address t1 rather
than L2's "arguments", which doesn't exist in L1. Actual
implementations can be more efficient than this, but this transform
is adequate to demonstrate lack of burden.
This is all onerous turd-polishing. It adds code bloat, memory-unsafe
implementation language usage attack surface, and memory bloat to the
implementation.
It also penalizes strict mode performance unless the implementator
sink yet more optimization cost (with attendant code/data/attack-
surface bloat) into this corner case. Why is that a good thing?
Your explicit desire to make strict mode the new language everyone
uses to write new code seems to me (a) quixotic; (b) wasteful of
energy better spent on getting rest args and the spread operator into
the language after 3.1.
I could be wrong about (a), but we can lay bets on the side. We should
not be gambling in the 3.1 spec in the very little time remaining, and
in the face of dissent from at least two (perhaps three if Pratap has
joined Maciej and me now) implementors.
Re #2: If foo is a strict function, then foo.arguments throws an
error. So the only functions where we need this eager creation are
those that either need arguments anyway, or those that directly use
the eval operator, both of which are statically visible.
Maciej's original (see bugs.ecmascript.org/ticket/428#comment: 5) was more qualified than my summary of it:
"2) arguments ever being a static array is problematic; unless
function.arguments is banned for functions with those semantics, it
would force a copy of the arguments to be made eagerly on entry to
such a function, if the requirement is to provide the original
arguments. If the requirement were "value of the arguments at the time
the arguments property is retrieved" it would not be so bad, but kind
of a weird semantic. "
Let's separate foo.arguments from the eager (eager enough) Array
creation and freezing issue. Item #2 is an objection to eager Array
instance creation and freezing for any strict mode function that uses
arguments freely in its body.
Re #3: Actual use of arguments aliasing is very rare. (Re bait,
compare to the frequency of feature testing.)
There's no point asserting "very rare" without evidence. Aliasing is
required for web compatible JS engines to compete in the market --
Google as well as Apple and Mozilla can testify to this. The question
is why would you make "use strict" a non-starter for anyone without
the time to do alias analysis on existing non-trivial code that would
otherwise benefit from strict mode?
We freeze arguments so most actual uses of such aliasing will result
in noisy failures.
Depending on code coverage!
Probably much current aliasing is more of a source of buggy behavior
than a feature used intentionally.
Another guess/assertion, but browser vendors cannot compete without
supporting aliasing. This suggests (but of course doesn't prove) that
"use strict" should not ban it.
This migration tax issue is secondary, a sideshow, since I'm not
confident "use strict" will take over (I hope it will; Perl's use
strict; gives us hope on this count; but as I keep warning, too much
gratuitous and puristic difference will make strict mode a dead
letter, or less used than you hope). The main objection is the first
one: implementation complexity burden.
It is good if strict mode exposes bugs in ported programs. New
programmers should learn the strict language first or only, so we
want to remove such landmines from it. Intentional use of aliasing
is probably about as common as intentional use of implicit wrapping,
and so your #3 would equally argue against the happy bug fix
agreement we just had.
No, those are different cases, requiring different arguments and not
wishful thinking or assertions. In particular, we can't get rid of
primitives and wrappers from the strict language, even if we avoid
implicit wrapping when |this|-binding (computing Reference bases). We
agree trying to do that breaks too much code (e.g., code that object
detects a property on a primitive, or makes an explicit wrapper to
amortize implicit wrapping costs in certain implementations).
Btw, I very clearly remember you agreeing in Kona that we should
freeze strict arguments for this reason. I raise this not to start a
"But you said. No you said" loop, but in hopes of jogging your
memory. Do you remember this?
I am 100% certain that I objected to making strict-mode arguments be
an actual Array instance. Do you remember this?
I also remember thinking through, and talking about, how
implementations that avoid creating an arguments object at all, e.g.
for arguments.length or arguments[i] expressions where i was constant
or could be bounded, could probably tolerate an "if (strict) throw"
addition to their custom setters or [[Put]] implementations.
Allowing strict errors on writes to arguments[i] addressed one aspect
of freezing, but I did not agree that aliasing was entirely out, for
the simple reason that I'm not certain aliasing via assignment to
formal parameters and reading back via arguments[i] is not the hard
case to keep working when adding "use strict" to existing codebases.
Really, we need more evidence, which could only be gained by trial
implementation and significant user testing.
The major bone of contention is whether strict mode should change
arguments from its magic object nature in ES1-3 to an Array instance.
Let's defer arguing about freezing (which may be ok as I allowed at
Kona) until we get past this disagreement.
But to return to the highest priority point: browser implementors are
telling you they do not want to make two arguments implementations.
On Jan 9, 2009, at 1:58 PM, Brendan Eich wrote:
Probably much current aliasing is more of a source of buggy
behavior than a feature used intentionally.Another guess/assertion, but browser vendors cannot compete without
supporting aliasing. This suggests (but of course doesn't prove)
that "use strict" should not ban it.
I really mean this: strict mode should ban things we know cause bugs,
where we can cite the bugs. Not just corner cases you or I find
unaesthetic. There's an element of "good taste" to strict mode's
design, for sure, but it is not the only consideration. We went around
the block on this point with respect to 'with', and I gave in.
Parameter aliasing is not a source of bugs in my experience. Even
'with' is not, compared to undefined properties being typo'd. I'm
concerned about disproportionate means being used (with opportunity
costs) by those of us working on the language, as well as with making
strict mode unusable on legacy code without too high a tax. I hope my
turd-polishing language doesn't offend, but it would be a shame if we
lingered over arguments at the expense of rest and spread.
On Fri, Jan 9, 2009 at 2:05 PM, Brendan Eich <brendan at mozilla.com> wrote:
On Jan 9, 2009, at 1:58 PM, Brendan Eich wrote:
Probably much current aliasing is more of a source of buggy behavior than
a feature used intentionally.
Another guess/assertion, but browser vendors cannot compete without supporting aliasing. This suggests (but of course doesn't prove) that "use strict" should not ban it.
I really mean this: strict mode should ban things we know cause bugs, where we can cite the bugs. Not just corner cases you or I find unaesthetic. There's an element of "good taste" to strict mode's design, for sure, but it is not the only consideration. We went around the block on this point with respect to 'with', and I gave in.
Parameter aliasing is not a source of bugs in my experience. Even 'with' is not, compared to undefined properties being typo'd. I'm concerned about disproportionate means being used (with opportunity costs) by those of us working on the language, as well as with making strict mode unusable on legacy code without too high a tax. I hope my turd-polishing language doesn't offend, but it would be a shame if we lingered over arguments at the expense of rest and spread.
No offense taken. And your points about opportunity costs are well taken. I'd certainly like to stop spending time arguing about arguments, in order to pay more attention to dozens of other more important things.
So let's imagine that we do nothing to "fix" arguments. How does magic unfixed arguments play with the rest of the spec? Some questions:
- Where is the semantic state that distinguishes magic arguments from other array-like non-arrays that inherit from Array.prototype? There needs to be such semantic state, to account for the acceptability of arguments as the second argument of Function.prototype.apply, to whit:
(From 15.3.4.3 of 22dec08 draft)
Otherwise, if argArray is neither an array nor an arguments object (see
10.3.2),
a TypeError exception is thrown. If argArray is either an array or an
arguments
object, [...]
Btw, that reference to 10.3.2 above looks like it should reference 10.5 Arguments Object, which says
The arguments object is created by calling the abstract operation CreateArgumentsObject with arguments [...]
There is no separate definition of CreateArgumentsObject. Rather, it seems its definition is the remaining text in 10.5. The spec here for non-strict arguments gives it a [[Class]] of "Object" and a [[Prototype]] of the original Array.prototype. It also, mysteriously, gives in a [[Constructor]] of the Object constructor. But there is no internal [[Constructor]] property!
So, leaving aside the probably mistaken [[Constructor]] property, Function.prototype.apply has no semantic state to inspect in order to carry out its spec.
- What happens when you freeze an arguments object?
In the Kona draft (and still in the 22dec08 draft), the joining of non-strict arguments with the parameter variables is specified by synthesizing getter/setter functions. At the Kona meeting, Waldemar (I think) pointed out 1) that the way this was specified had inadvertent non-hygienic lexical variable capture problems, and 2) given the new attribute revelation APIs, such explanatory getters and setters could be reified, requiring us to specify whether they're frozen, when they're ===, etc. Rather than fixing these issues, at Kona we decided instead not to specify them in terms of getters and setters. But then, what does Object.getOwnPropertyDescriptor(arguments, '3') return? If it claims arguments[3] is a writable data property, then one should be able to freeze it. Once frozen, does this prevent writing the corresponding parameter variable? Or does it sever the joining of arguments[3] and that parameter variable? If instead it is specified with a non-configurable getter/setter pair, then freezing does not affect it.
- What happens when you write to an object that inherits from an arguments object?
Given var sub = Object.create(arguments); sub[3] = foo;
does this assignment modify the parameter variable joined to arguments[3]? Once again, if we think of arguments[3] as a data property, then the answer should be no. We've instead created a new own '3' property on sub and have not affected its parent. But if we think of it as an accessor property, then this assignment should invoke the inherited setter.
Whatever we do with strict arguments, we need to answer all three of these questions for non-strict arguments anyway. Until we settle the semantics of non-strict arguments, it's premature to try to determine whether strict arguments can live with that semantics.
On Jan 14, 2009, at 2:54 PM, Mark S. Miller wrote:
So let's imagine that we do nothing to "fix" arguments. How does
magic unfixed arguments play with the rest of the spec? Some
questions:
- Where is the semantic state that distinguishes magic arguments
from other array-like non-arrays that inherit from Array.prototype?
There needs to be such semantic state, to account for the
acceptability of arguments as the second argument of
Function.prototype.apply, to whit:(From 15.3.4.3 of 22dec08 draft)
Otherwise, if argArray is neither an array nor an arguments object
(see 10.3.2), a TypeError exception is thrown. If argArray is either an array or
an arguments object, [...]Btw, that reference to 10.3.2 above looks like it should reference
10.5 Arguments Object, which saysThe arguments object is created by calling the abstract operation CreateArgumentsObject with arguments [...]
There is no separate definition of CreateArgumentsObject. Rather, it
seems its definition is the remaining text in 10.5. The spec here
for non-strict arguments gives it a [[Class]] of "Object" and a
[[Prototype]] of the original Array.prototype. It also,
mysteriously, gives in a [[Constructor]] of the Object constructor.
But there is no internal [[Constructor]] property!
These seem like easily fixed 3.1 bugs. File 'em?
So, leaving aside the probably mistaken [[Constructor]] property,
.constructor must be what's meant.
js> function f(){return arguments.constructor}
js> f()
function Object() { [native code] }
I'm not any more fond of this than you, but it is what it is.
Function.prototype.apply has no semantic state to inspect in order
to carry out its spec.
When in doubt, use brute force. ES3 already did. Quoting from its spec
for Function.prototype.apply:
15.3.4.3 Function.prototype.apply (thisArg, argArray)
The apply method takes two arguments, thisArg and argArray, and
performs a function call using the [[Call]] property of the object. If
the object does not have a [[Call]] property, a TypeError exception is
thrown.
If thisArg is null or undefined, the called function is passed the
global object as the this value. Otherwise, the called function is
passed ToObject(thisArg) as the this value.
If argArray is null or undefined, the called function is passed no
arguments. Otherwise, if argArray is neither an array nor an arguments
object (see section 10.1.8), a TypeError exception is thrown. If
argArray is either an array or an arguments object, the function is
passed the (ToUint32(argArray.length)) arguments argArray[0],
argArray[1], …, argArray[ToUint32(argArray.length)–1].
The length property of the apply method is 2.
- What happens when you freeze an arguments object?
In the Kona draft (and still in the 22dec08 draft), the joining of
non-strict arguments with the parameter variables is specified by
synthesizing getter/setter functions. At the Kona meeting, Waldemar
(I think) pointed out 1) that the way this was specified had
inadvertent non-hygienic lexical variable capture problems, and 2)
given the new attribute revelation APIs, such explanatory getters
and setters could be reified, requiring us to specify whether
they're frozen, when they're ===, etc.
Yeah, Waldemar and I both pointed out (IIRC I did this first :-P) that
this overspecifies arguments.
Rather than fixing these issues, at Kona we decided instead not to
specify them in terms of getters and setters. But then, what does
Object.getOwnPropertyDescriptor(arguments, '3') return? If it claims
arguments[3] is a writable data property, then one should be able to
freeze it.
Yes.
Once frozen, does this prevent writing the corresponding parameter
variable?
No, not by assigning to the parameter name.
Or does it sever the joining of arguments[3] and that parameter
variable?
Yes.
If instead it is specified with a non-configurable getter/setter
pair, then freezing does not affect it.
But that overspecifies.
- What happens when you write to an object that inherits from an
arguments object?
No setters (overspecifies), so the same thing as today:
js> function f(){}
js> f.prototype = (function(){return arguments})() [object Object] js> o = new f [object Object] js> o[0] = 42
42 js> o[0]
42 js> function g(){}
js> g.prototype = (function(){return arguments})(1,2,3) [object Object] js> p = new g [object Object] js> p[0]
1 js> p[1]
2 js> p[2]
3 js> p[0]=4
4 js> p[0]
4 js> seal(p.proto)
js> p[0]=5
5 js> p[0]
5 js> g.prototype[0]=6
typein:19: Error: g.prototype[0] is read-only
(seal is a SpiderMonkey js shell function that calls an API to freeze
the object in 3.1 terms.)
Given var sub = Object.create(arguments); sub[3] = foo;
does this assignment modify the parameter variable joined to
arguments[3]?
No, no setter in the prototype that is called instead of shadowed.
Instead, shadowing as happens in ES1-3.
Once again, if we think of arguments[3] as a data property, then the
answer should be no. We've instead created a new own '3' property on
sub and have not affected its parent. But if we think of it as an
accessor property, then this assignment should invoke the inherited
setter.
Accessor overspecifies. Leave it underspecified and avoid malinvesting
in arguments polishing!
Whatever we do with strict arguments, we need to answer all three of
these questions for non-strict arguments anyway.
I tend to think the answers should be the same, although as noted in a
previous message I'm open to the idea of freezing strict arguments.
Until we settle the semantics of non-strict arguments, it's
premature to try to determine whether strict arguments can live with
that semantics.
Hope the above helps,
Mark S. Miller wrote:
- Where is the semantic state that distinguishes magic arguments from other array-like non-arrays that inherit from Array.prototype? There needs to be such semantic state, to account for the acceptability of arguments as the second argument of Function.prototype.apply, to whit:
(From 15.3.4.3 of 22dec08 draft)
Otherwise, if argArray is neither an array nor an arguments object (see 10.3.2), a TypeError exception is thrown. If argArray is either an array or an arguments object, [...]
That can be fixed by making Function.prototype.apply accept any array-like object (i.e. only read the .length and array index properties of argArray). I've previously suggested wording for that, which is repeated at the end of this email.
- What happens when you freeze an arguments object?
I don't think this presents any problem: the synthesized getter/setter properties are made non-[[Configurable]] and the object is made non-[[Extensible]], just like when freezing any other object.
In the Kona draft (and still in the 22dec08 draft), the joining of non-strict arguments with the parameter variables is specified by synthesizing getter/setter functions. At the Kona meeting, Waldemar (I think) pointed out 1) that the way this was specified had inadvertent non-hygienic lexical variable capture problems,
Why is it non-hygienic? It captures the variables, certainly, but it's supposed to.
and 2) given the new attribute revelation APIs, such explanatory getters and setters could be reified, requiring us to specify whether they're frozen, when they're ===, etc.
Seems fine to me. Don't we have to specify what attributes they have? An arguments object should not be a magical special case for attribute reflection. The behaviour of === is already clear in the 22 December draft, because it specifies that the getters/setters are initialized as-if by creating new functions.
- What happens when you write to an object that inherits from an arguments object?
Given var sub = Object.create(arguments); sub[3] = foo;
does this assignment modify the parameter variable joined to arguments[3]? Once again, if we think of arguments[3] as a data property, then the answer should be no.
(Non-strict) arguments properties are not data properties, period. They can't be because that would not result in the required aliasing with parameters.
We've instead created a new own '3' property on sub and have not affected its parent. But if we think of it as an accessor property, then this assignment should invoke the inherited setter.
Right. This is arguably a little bit overspecified, but mostly harmless.
Whatever we do with strict arguments, we need to answer all three of these questions for non-strict arguments anyway.
I agree; I just think the current wording already does answer these -- subject to the Function.prototype.apply fix below, which is independently useful.
There should be a Trac issue for Function.prototype.apply; I'll be busy for the next couple of days, so feel free to add one.
The following changes
- make apply handle its argArray generically;
- rewrite apply and call using the algorithm notation;
- clarify that apply and call return the return value of the called function;
- acknowledge that there may be an implementation-defined limit on the number of arguments to a function. (This also needs to be fixed in section 11.2.4.)
==== 15.3.4.3 Function.prototype.apply (thisArg, argArray)
The apply method takes two arguments, thisArg and argArray, and performs a function call using the [[Call]] property of the this object, by the following steps:
- If IsCallable(this) is false, then throw a TypeError exception.
- If argArray is null or undefined, then a. Call the [[Call]] method of this object, providing thisArg as the this value and no arguments. b. Return Result(2a).
- If Type(argArray) is not Object, then throw a TypeError exception.
- Call the [[Get]] method of argArray with property name "length".
- Let n be ToArrayLength(Result(4)).
- If n is greater than an implementation-defined limit on the number of arguments to a function, then throw a RangeError exception.
- For every nonnegative integer k less than n: a. Let P_k be ToString(k). b. Let arg_k be the result of calling the [[Get]] method of argArray with property name P_k.
- Call the [[Call]] method of this object, providing thisArg as the this value and arg_0, arg_1, ... arg_{n-1} as the arguments.
- Return Result(8).
The length property of the apply method is 2.
15.3.4.4 Function.prototype.call (thisArg [ , arg_0 [ , arg_1, ... ] ] )
The call method takes one or more arguments, thisArg and (optionally) arg_0, arg_1 etc., and performs a function call using the [[Call]] property of the object, by the following steps:
- If IsCallable(this) is false, then throw a TypeError exception.
- Call the [[Call]] method of this object, providing thisArg as the this value, and any remaining arguments to the call method starting with arg_0 (if provided) as the arguments.
- Return Result(2).
The length property of the call method is 1.
On Wed, Jan 14, 2009 at 7:08 PM, Brendan Eich <brendan at mozilla.com> wrote:
When in doubt, use brute force. ES3 already did. Quoting from its spec for Function.prototype.apply:
15.3.4.3 Function.prototype.apply (thisArg, argArray) [...] Otherwise, if *argArray *is neither an array nor an arguments object (see section 10.1.8), a *TypeError *exception is thrown. If *argArray is either an array or an arguments object, the function is passed the (ToUint32(argArray.length)) arguments argArray[0], argArray[1], …, * argArray[ToUint32(argArray.length)–1]. [...]
Yes, this is the spec text I quoted as well. But I don't understand it or your "use brute force" answer. How does Function.prototype.apply determine that argArray is an arguments object? What semantic state distinguishes it from any other array-like non-array object that inherits from Array.prototype? If an implementation is necessarily required to associate such extra semantic state in order to conform to the spec, shouldn't the spec admit that
-
at least by introducing an internal property for that purpose? After all, that's what the internal properties are for.
-
Alternatively, if its [[Class]] were "Arguments", Function.prototype.apply could test whether the [[Class]] were "Array" or "Arguments".
-
Or we could take David-Sarah's suggestion and generalize Function.prototype.apply to accept any array-like object as its second argument.
We need to do something, since as it stands the operational spec is unimplementable in terms of the specified semantic state.
On Jan 14, 2009, at 9:16 PM, Mark S. Miller wrote:
On Wed, Jan 14, 2009 at 7:08 PM, Brendan Eich <brendan at mozilla.com>
wrote:When in doubt, use brute force. ES3 already did. Quoting from its
spec for Function.prototype.apply:15.3.4.3 Function.prototype.apply (thisArg, argArray) [...] Otherwise, if argArray is neither an array nor an arguments
object (see section 10.1.8), a TypeError exception is thrown. If
argArray is either an array or an arguments object, the function is
passed the (ToUint32(argArray.length)) arguments argArray[0],
argArray[1], …, argArray[ToUint32(argArray.length)–1]. [...]Yes, this is the spec text I quoted as well. But I don't understand
it or your "use brute force" answer.
Sorry, didn't mean to rehash.
How does Function.prototype.apply determine that argArray is an
arguments object?
ES1-3 leave this unspecified. But they talk about "an arguments object
[being] created" (10.1.8), etc. and it's easy enough to implement the
spec -- there are many implementations. SpiderMonkey uses a distinct
class whose name is "Object", to satisfy what is specified
([[Prototype]] is the original Object.prototype value). Do we need
really need to say more?
We need to do something, since as it stands the operational spec is
unimplementable in terms of the specified semantic state.
The spec has a domain of discourse that includes "an arguments
object". It's implementable without error. Sure, we could add another
internal property to avoid this appeal to an unspecified way of
distinguish arguments objects from all other objects. Again I'm loath
to over-invest.
Sorry for being late to the party.
Is there a reason why we need to restrict the argArray to apply this much? I think it would be very useful to allow anything array like here.
On Jan 15, 2009, at 9:50 AM, Erik Arvidsson wrote:
Sorry for being late to the party.
Is there a reason why we need to restrict the argArray to apply this much? I think it would be very useful to allow anything array like here.
Please define "array like" and give examples (possibly including DOM
nodelists, which thanks to "live update" are are neither lists nor
arrays, rather more like cursors).
We had a structural type for it in ES4. ES3.1 has no such type system
or spec language. It's too late to add anything like this. But for
Harmony, proposing (possibly more than one) "array-like" notion that
could be used to extend the language is fair game.
At Tuesday's ES3.1 conference call we discussed the three major outstanding issues regarding the arguments object that had been discussed on this list and made decisions for incorporation into the "Mountain View" draft that was released earlier today. These decisions were: Ticket #428: what does Array.isArray(arguments) return? Decision - false Ticket #447: should arguments object/formal parameter sharing continue after return? Decision - yes Ticket #448: should the sharing semantics of the arguments object differ for strict functions? Decision - no
At the time we thought these decisions reflected the emerging consensuses on the discussion list. However, Mark Miller was not at the Tuesday meeting and at today's meeting he argued that the discussion list consensus for #448 seemed to be swing towards yes.
This would be a relatively straight forward change to the loatest draft specification. I suggest we make the decision at the Mountain View meeting after everybody has had a chance to review the current specification text.
The hard part about specifying arguments isn't any possible strict mode restrictions. Instead, the challenge is understanding and specifying for normal (non-strict mode) the full impact of the ES3.1 enhanced object model (accessor properties, reified attributes, etc.) on the semantics of the arguments object. I think we are very close to having it right now (but please review the spec. carefully) and most of the issues below are addressed so see my inline comments below.
-----Original Message----- From: es3.x-discuss-bounces at mozilla.org [mailto:es3.x-discuss- bounces at mozilla.org] On Behalf Of David-Sarah Hopwood Sent: Wednesday, January 14, 2009 8:01 PM To: es-discuss at mozilla.org; es3.x-discuss at mozilla.org Subject: Re: Bait taken: Arguments about arguments
Mark S. Miller wrote:
- Where is the semantic state that distinguishes magic arguments from other array-like non-arrays that inherit from Array.prototype? There needs to be such semantic state, to account for the acceptability of arguments as the second argument of Function.prototype.apply, to whit:
(From 15.3.4.3 of 22dec08 draft)
Otherwise, if argArray is neither an array nor an arguments object (see 10.3.2),
a TypeError exception is thrown. If argArray is either an array or an arguments object, [...]
That can be fixed by making Function.prototype.apply accept any array- like object (i.e. only read the .length and array index properties of argArray). I've previously suggested wording for that, which is repeated at the end of this email.
This is already adequately addressed by the current specification which consistently speaks about the "arguments object". We could invent a seemingly more formal specification device to identify arguments object (for example using a [[ArgumentsObject]] internal property to tag them) but that really doesn't add anything. These are all just specification devices (it's up to an implementation to figure out how to achieve the specified semantics) and I don't see any real ambiguity in the specification about which objects are "arguments objects".
That said, I do think there is ambiguity about what is required of the second argument to Function.prototype.apply. As there seems to be a subthread specifically on that, I'll save my thoughts for there.
- What happens when you freeze an arguments object?
I don't think this presents any problem: the synthesized getter/setter properties are made non-[[Configurable]] and the object is made non-[[Extensible]], just like when freezing any other object.
In the Kona draft (and still in the 22dec08 draft), the joining of non-strict arguments with the parameter variables is specified by synthesizing getter/setter functions. At the Kona meeting, Waldemar (I think) pointed out 1) that the way this was specified had inadvertent non-hygienic lexical variable capture problems,
Why is it non-hygienic? It captures the variables, certainly, but it's supposed to.
and 2) given the new attribute revelation APIs, such explanatory getters and setters could be reified, requiring us to specify whether they're frozen, when they're ===, etc.
Seems fine to me. Don't we have to specify what attributes they have? An arguments object should not be a magical special case for attribute reflection. The behaviour of === is already clear in the 22 December draft, because it specifies that the getters/setters are initialized as-if by creating new functions.
The current draft still uses closures to model the sharing semantics but is now very careful to not expose them. If examined using getOwnPropertyDescriptor the indexed arguments properties appear as data properties. If a property is modified via Object.defineProperty or delete the sharing semantics is "broken" when appropriate. This is consistent with existing implementations which appear to "disconnect" arguments indexed properties that are deleted and then subsequently recreated. After talking with Mark at today's meeting I think there is one addition case that isn't accounted for in the current draft: setting [[Writable]]:false doesn't currently break sharing for an arguments property. It should. I'll ticket this as a bug in the current draft.
- What happens when you write to an object that inherits from an arguments object?
Given var sub = Object.create(arguments); sub[3] = foo;
does this assignment modify the parameter variable joined to arguments[3]? Once again, if we think of arguments[3] as a data property, then the answer should be no.
No is the answer that is implemented in the latest draft. All the normal semantics of assignment to inherited data properties work (according to the specification) for inherited indexed arguments object properties.
(Non-strict) arguments properties are not data properties, period. They can't be because that would not result in the required aliasing with parameters.
For purposed of reflection they have to be either data properties or accessor properties because that is all we have. In the latest spec. they manifest themselves via getOwnPropertyDescriptor as data properties. Rather than thinking of them as accessor properties it is probably better to think about them as data properties that are being continually monitored by some agent which updates them to reflect changes to the corresponding formals
We've instead created a new own '3' property on sub and have not affected its parent. But if we think of it as an accessor property, then this assignment should invoke the inherited setter.
Right. This is arguably a little bit overspecified, but mostly harmless.
See above, they act as data properties
Whatever we do with strict arguments, we need to answer all three of these questions for non-strict arguments anyway.
I agree; I just think the current wording already does answer these -- subject to the Function.prototype.apply fix below, which is independently useful.
The ES3 spec. certainly does answer these, it doesn't have accessor properties or user settable property attributes so it couldn't address these issues at all. Also it doesn't explicitly address any of the ambiguous edge cases of what it means when it says "the property shares its value with the corresponding property of the activation object". The Kona draft manifested the argument properties as accessor properties but that has now been fixed.
There should be a Trac issue for Function.prototype.apply; I'll be busy for the next couple of days, so feel free to add one.
The following changes
- make apply handle its argArray generically;
- rewrite apply and call using the algorithm notation;
- clarify that apply and call return the return value of the called function;
- acknowledge that there may be an implementation-defined limit on the number of arguments to a function. (This also needs to be fixed in section 11.2.4.)
I generally agree, these really should be specified algorithmically. Otherwise we don't really know how they're supposed to work for edge cases such as sparse arrays. If we had an algorithmic specification we probably won't need to worry about restricting the second argument to arrays and arguments objects.
Brendan Eich wrote:
On Jan 15, 2009, at 4:54 PM, David-Sarah Hopwood wrote:
Brendan Eich wrote:
On Jan 15, 2009, at 9:50 AM, Erik Arvidsson wrote:
Is there a reason why we need to restrict the argArray to apply this much? I think it would be very useful to allow anything array like here.
Please define "array like" and give examples (possibly including DOM nodelists, which thanks to "live update" are are neither lists nor arrays, rather more like cursors).
"array-like" means having 'length' and array index properties.
And the constraint that index < length for all index properties?
No.
And other constraints from ES1-3 [section 15.4] as follows?
No.
If I'd meant to include those constraints, I would have said so. The constraints are not needed here. You just read 'length' using [[Get]], and then read each array index property from 0 to ToUint32(length)-1 inclusive using [[Get]]. This is how all of the generic methods that operate on array-like objects work.
I think you're making mountains out of molehills in this subthread; a Function.prototype.apply that treats its arguments array generically is quite easy to specify (and implement).
On Jan 15, 2009, at 7:27 PM, David-Sarah Hopwood wrote:
Brendan Eich wrote:
On Jan 15, 2009, at 4:54 PM, David-Sarah Hopwood wrote:
Brendan Eich wrote:
On Jan 15, 2009, at 9:50 AM, Erik Arvidsson wrote:
Is there a reason why we need to restrict the argArray to apply
this much? I think it would be very useful to allow anything array like here.Please define "array like" and give examples (possibly including
DOM nodelists, which thanks to "live update" are are neither lists nor arrays, rather more like cursors)."array-like" means having 'length' and array index properties.
And the constraint that index < length for all index properties?
No.
And other constraints from ES1-3 [section 15.4] as follows?
No.
If I'd meant to include those constraints, I would have said so.
Bully for you. What I'm looking for is your rationale.
The constraints are not needed here. You just read 'length' using [[Get]], and then read each array index property from 0 to ToUint32(length)-1 inclusive using [[Get]]. This is how all of the generic methods that operate on array-like objects work.
Good, this is the rationale I sought.
I think you're making mountains out of molehills in this subthread; a Function.prototype.apply that treats its arguments array generically is quite easy to specify (and implement).
I agree, and I've made no mountain. You must be thinking of some other
"subthread". If asking you for rationale makes a mountain, then climb
it.
On Thu, Jan 8, 2009 at 9:30 PM, Brendan Eich <brendan at mozilla.com> wrote:
Regarding your inline
the ticket:
and
next face to face.
We have divergent memories of what was agreed. This is an issue I care about a lot, so I'm confident I didn't simply forget. Probably we misunderstood each other somehow. In any case, what matters now is that we come to agreement.
You write (with bullets turned into numbers):
but web compatibility plus strict mode would seem to require it for foo.arguments. 3) The migration tax for strict mode should not be hiked to include "find all the hidden aliasing dependencies in the legacy codebase you inherited".
Re #1: Allen and Crock relayed this concern. I am skeptical, as there's a trivial source-to-source transformation from the language L1 that I would prefer to program in to the language L2 that you would prefer to implement:
If strict function foo in language L1 mentions 'arguments' freely or contains an occurrence of the eval operator (this is statically determinable, and we do not count either 'arguments' or 'eval' in nested functions), for example
then translate it to strict function foo in language L2, where t1 represents an variable name not otherwise mentioned in the program, as
In other words, in exactly these functions, snapshot arguments eagerly into a genuine array, and then replace all further uses of arguments with the variable holding that snapshot. It's not quite a source-to-source translation, since additional magic is required for a free 'arguments' within an eval()ed script to address t1 rather than L2's "arguments", which doesn't exist in L1. Actual implementations can be more efficient than this, but this transform is adequate to demonstrate lack of burden.
Re #2: If foo is a strict function, then foo.arguments throws an error. So the only functions where we need this eager creation are those that either need arguments anyway, or those that directly use the eval operator, both of which are statically visible.
Re #3: Actual use of arguments aliasing is very rare. (Re bait, compare to the frequency of feature testing.) We freeze arguments so most actual uses of such aliasing will result in noisy failures. Probably much current aliasing is more of a source of buggy behavior than a feature used intentionally. It is good if strict mode exposes bugs in ported programs. New programmers should learn the strict language first or only, so we want to remove such landmines from it. Intentional use of aliasing is probably about as common as intentional use of implicit wrapping, and so your #3 would equally argue against the happy bug fix agreement we just had.
Btw, I very clearly remember you agreeing in Kona that we should freeze strict arguments for this reason. I raise this not to start a "But you said. No you said" loop, but in hopes of jogging your memory. Do you remember this?