The global object as the "global scope instance object"

Brendan Eich brendan at mozilla.org
Mon Jan 23 10:25:02 PST 2012


> Andreas Rossberg <mailto:rossberg at google.com>
> January 23, 2012 10:15 AM
> On 23 January 2012 18:39, Brendan Eich<brendan at mozilla.org>  wrote:
>> Andreas Rossberg<mailto:rossberg at google.com>
>>> In other words, the current behaviour breaks both scoping and objects
>>> semantics.
>> Non-strict or really pre-strict implementations broke scoping semantics --
>> no temporal dead zone. Whether we can retroactively force the new semantics
>> on any JS that worked with the old scoping semantics remains to be seen.
>>
>> Object semantics, I think you misread the alert output. Or did I?
>
> Big oops, you are right. How embarrassing! I apologize to Gavin for
> misrepresenting!
>
> The scoping issue remains, though. Existing implementations are
> seriously broken there. For example, for some compatibility reasons,
> V8 currently allows
>
>    var w = 1; w = 2; print(w); const w = 3
>
> which will output 2. The idea most likely was that const should behave
> like var. This, and other, similar examples clearly have to break if
> should const become official in classic mode, so the compatibility
> argument may not carry far.

SpiderMonkey:

js> var w = 1; w = 2; print(w); const w = 3
typein:19: TypeError: redeclaration of var w:
typein:19: var w = 1; w = 2; print(w); const w = 3
typein:19: ....................................^

js> var w = 1
js> const w = 3
typein:22: TypeError: redeclaration of var w

Why does V8 do something else, do you know the impetus for diverging?

>> This is future-hostile to guards, or really, we kick the can down the road a
>> bit and evangelize strict mode. If and when we add guards, we can either
>> break scoping semantics backward compatibility, or require strict opt in.
>
> Doesn't sound pleasant to me... :(

Politicians do kick the can all the time. Oh wait.

We can pay the piper now. I will try to get non-strict const in 
SpiderMonkey into alignment, see what breaks. May be hard to find the 
thing we ca't afford to break quickly, of course.

/be
>
> /Andreas
> _______________________________________________
> es-discuss mailing list
> es-discuss at mozilla.org
> https://mail.mozilla.org/listinfo/es-discuss
>
> Brendan Eich <mailto:brendan at mozilla.org>
> January 23, 2012 9:39 AM
>> Andreas Rossberg <mailto:rossberg at google.com>
>> January 23, 2012 5:54 AM
>> On 22 January 2012 23:46, Gavin Barraclough<barraclough at apple.com> 
>> wrote:
>>> On Jan 20, 2012, at 4:23 PM, Allen Wirfs-Brock wrote:
>>>> I'm not yet convinced that you need to publicly expose such global 
>>>> object
>>>> properties accessor properties. I think it is possible to hide the
>>>> mechanisms.
>>> I'm strongly inclined to agree with this sentiment – engine 
>>> implementors
>>> might choose internally to utilize accessors in their 
>>> implementations, but
>>> from a user perspective it seems to make much more sense that these 
>>> remain
>>> as regular data properties. It is worth bearing in mind that 'const' is
>>> already available outside of strict mode and we need to be 
>>> considerate of
>>> any breaking changes we make to existing semantics.
>>
>> Sigh. This may be exactly the reason why I am deeply sceptical that
>> backporting ES6 features to classic mode is a wise move. Now we don't
>> just have to stay backwards compatible with broken ES5 features, we
>> even have to stay backwards compatible with broken ES5 non-features?
>> Even in strict mode?
>>
>> Harmony was supposed to be based on strict mode for a reason.
>
> Hang on -- I think Gavin may have stated things in a way that set the 
> cat among pigeons unnecessarily. We agreed (at least Gavin and I) at 
> the f2f to try to bring our const and function-in-block 
> implementations in line with non-strict ES6 in the version-free opt-in 
> model toward which we're striving.
>
> In this light I do not think we should preemptively concede anything 
> to breaking changes of existing *implementation* semantics.
>
> OTOH the rest of Gavin's mail invoked existing implementations 
> (Firefox in particular) more for intuitive non-strict appeal than as 
> binding precedent, if I read Gavin right.
>
> On *how* to implement the suggested semantics, I do see disagreement. 
> Indeed I hope we do not need to introduce not-yet-defined as a value 
> state in the object model. But the user-facing non-strict semantics 
> should be separable.
>
>
>>> (We could define
>>> incompatible semantics for strict-mode only, but this would likely 
>>> lead to a
>>> confusing divergence in semantics for users – it could be horribly 
>>> confusing
>>> if const variables in non-strict code were to be implemented as data
>>> properties whilst those in strict code were defined as accessor 
>>> properties).
>>>
>>> alert('value' in Object.getOwnPropertyDescriptor(this, 'x'));
>>> alert(Object.getOwnPropertyDescriptor(this, 'x').writable === true);
>>>
>>> alert(Object.getOwnPropertyDescriptor(this, 'x').value);
>>> x = 1;
>>> alert(Object.getOwnPropertyDescriptor(this, 'x').value);
>>> const x = 2;
>>> alert(Object.getOwnPropertyDescriptor(this, 'x').value);
>>> x = 3;
>>> alert(Object.getOwnPropertyDescriptor(this, 'x').value);
>>>
>>> This script tests the presence and writability of a property on the 
>>> global
>>> object, and the effect of attempting to assign to it in non-strict 
>>> code. It
>>> demonstrates a non-const var behaving as a regular non-writable 
>>> property in
>>> non-strict code.
>>>
>>> On FireFox the above script outputs "true, false, undefined, 
>>> undefined, 2,
>>> 2", which to my mind makes a lot of sense. It perfectly matches, as 
>>> far as
>>> one can test, any other regular non writable property that you could 
>>> create
>>> on an object (of course there it not normally the opportunity to split
>>> creation and initialization of a non-writable property).
>>
>> I disagree completely. This is the current V8 semantics as well, but
>> it does _not_ make sense.
>>
>> You observe that the value of a const is changing in the middle of
>> your program. That fundamentally violates const semantics, and is
>> incompatible with the idea of guards on let as well.
>
> Yes, we agree to all that for strict code (ES5 or above), but this is 
> non-strict code. In non-strict code, implementations have already 
> violated const semantics, and we have not added guards yet.
>>
>> Furthermore, the property descriptor tells you that the property is a
>> writable data property,
>
> No -- false is the second value alerted.
>
>> but you cannot write it (e.g. replacing "x =
>> 1" with "this.x = 1"). That violates the object model.
>>
>> In other words, the current behaviour breaks both scoping and objects 
>> semantics.
>
> Non-strict or really pre-strict implementations broke scoping 
> semantics -- no temporal dead zone. Whether we can retroactively force 
> the new semantics on any JS that worked with the old scoping semantics 
> remains to be seen.
>
> Object semantics, I think you misread the alert output. Or did I?
>
>
>>> This behaviour is also sensibly consistent with that for let and var.
>>> Running the above script, changing 'const' to 'var'
>>> outputs "true, true, undefined, 1, 2, 3" (indicating the property is
>>> writable, and all assignments succeed), and for 'let' currently also
>>> outputs "true, true, undefined, 1, 2, 3" on FireFox. To my mind this
>>> behaviour should probably change slightly. With a temporal dead zone
>>> implemented I would expect an attempt to [[DefineOwnProperty]] to an
>>> uninitialized value to be rejected (though I would not expect an 
>>> exception
>>> to be thrown from non-strict code) so I would expect the output for 
>>> 'let' to
>>> be "true, true, undefined, undefined, 2, 3" (the assignment of 'a' 
>>> silently
>>> fails leaving the value unchanged).
>>
>> More importantly, an attempt to [[Get]] an uninitialized property also
>> has to be rejected. Both requires proliferating the entire notion of
>> being uninitialized into the object model. To be coherent, you'd also
>> have to be able to reflect on it through property descriptors, make it
>> available to proxies, and so on. I see no good reason why we should go
>> there.
>
> Agreed. And for non-strict code if we choose to keep the non-throwing 
> behavior, we do not have to. We do however therefore have to allow a 
> const to be read before initialized, with value undefined -- but only 
> for non-strict code.
>
> This is future-hostile to guards, or really, we kick the can down the 
> road a bit and evangelize strict mode. If and when we add guards, we 
> can either break scoping semantics backward compatibility, or require 
> strict opt in.
>
> /be
> Andreas Rossberg <mailto:rossberg at google.com>
> January 23, 2012 5:54 AM
> On 22 January 2012 23:46, Gavin Barraclough<barraclough at apple.com>  wrote:
>> On Jan 20, 2012, at 4:23 PM, Allen Wirfs-Brock wrote:
>>> I'm not yet convinced that you need to publicly expose such global object
>>> properties accessor properties.  I think it is possible to hide the
>>> mechanisms.
>> I'm strongly inclined to agree with this sentiment – engine implementors
>> might choose internally to utilize accessors in their implementations, but
>> from a user perspective it seems to make much more sense that these remain
>> as regular data properties.  It is worth bearing in mind that 'const' is
>> already available outside of strict mode and we need to be considerate of
>> any breaking changes we make to existing semantics.
>
> Sigh. This may be exactly the reason why I am deeply sceptical that
> backporting ES6 features to classic mode is a wise move. Now we don't
> just have to stay backwards compatible with broken ES5 features, we
> even have to stay backwards compatible with broken ES5 non-features?
> Even in strict mode?
>
> Harmony was supposed to be based on strict mode for a reason.
>
>
>>   (We could define
>> incompatible semantics for strict-mode only, but this would likely lead to a
>> confusing divergence in semantics for users – it could be horribly confusing
>> if const variables in non-strict code were to be implemented as data
>> properties whilst those in strict code were defined as accessor properties).
>>
>> alert('value' in Object.getOwnPropertyDescriptor(this, 'x'));
>> alert(Object.getOwnPropertyDescriptor(this, 'x').writable === true);
>>
>> alert(Object.getOwnPropertyDescriptor(this, 'x').value);
>> x = 1;
>> alert(Object.getOwnPropertyDescriptor(this, 'x').value);
>> const x = 2;
>> alert(Object.getOwnPropertyDescriptor(this, 'x').value);
>> x = 3;
>> alert(Object.getOwnPropertyDescriptor(this, 'x').value);
>>
>> This script tests the presence and writability of a property on the global
>> object, and the effect of attempting to assign to it in non-strict code.  It
>> demonstrates a non-const var behaving as a regular non-writable property in
>> non-strict code.
>>
>> On FireFox the above script outputs "true, false, undefined, undefined, 2,
>> 2", which to my mind makes a lot of sense.  It perfectly matches, as far as
>> one can test, any other regular non writable property that you could create
>> on an object (of course there it not normally the opportunity to split
>> creation and initialization of a non-writable property).
>
> I disagree completely. This is the current V8 semantics as well, but
> it does _not_ make sense.
>
> You observe that the value of a const is changing in the middle of
> your program. That fundamentally violates const semantics, and is
> incompatible with the idea of guards on let as well.
>
> Furthermore, the property descriptor tells you that the property is a
> writable data property, but you cannot write it (e.g. replacing "x =
> 1" with "this.x = 1"). That violates the object model.
>
> In other words, the current behaviour breaks both scoping and objects semantics.
>
>
>> This behaviour is also sensibly consistent with that for let and var.
>>   Running the above script, changing 'const' to 'var'
>> outputs "true, true, undefined, 1, 2, 3" (indicating the property is
>> writable, and all assignments succeed), and for 'let' currently also
>> outputs "true, true, undefined, 1, 2, 3" on FireFox.  To my mind this
>> behaviour should probably change slightly.  With a temporal dead zone
>> implemented I would expect an attempt to [[DefineOwnProperty]] to an
>> uninitialized value to be rejected (though I would not expect an exception
>> to be thrown from non-strict code) so I would expect the output for 'let' to
>> be "true, true, undefined, undefined, 2, 3" (the assignment of 'a' silently
>> fails leaving the value unchanged).
>
> More importantly, an attempt to [[Get]] an uninitialized property also
> has to be rejected. Both requires proliferating the entire notion of
> being uninitialized into the object model. To be coherent, you'd also
> have to be able to reflect on it through property descriptors, make it
> available to proxies, and so on. I see no good reason why we should go
> there.
>
>> I would suggest that specifying this as a piece of additional hidden
>> internal state on a data descriptor (rejecting attempts to define or access
>> the value prior to initialization, throwing in strict-mode&  silently
>> ignoring in non-strict)
>
> Hidden internal state? That seems to undermine the very idea of having
> property descriptors to reflect the state of a property.
>
>> makes most sense on a number of grounds:
>>
>>   – compatibility with existing non-strict const implementations.
>
> Yeah, I think there is no reasonable way we can achieve that. Existing
> const implementations aren't even compatible with each other. Const
> was ruled out in strict mode for a reason.
>
>>   – consistency with appearance of var properties on the global object.
>
> Var is not lexically scoped, and has a very different semantics from
> let. Breaking "consistency" with var is the whole point of lexical
> scoping.
>
>>   – consistency with appearance of regular non-writable properties on other
>> objects.
>>   – consistency with behaviour of access to regular non-writable properties
>> on other objects.
>
> It's not consistent, otherwise you couldn't observe writable === true,
> and wouldn't need "hidden internal state".
>
>>   – data properties typically have higher performance in implementations,
>> specifying all global let&  const properties to be accessors may introduce
>> unnecessary overhead.
>
> Actually, as a data point, performance of the toplevel is currently
> poor in V8, _because_ every variable is reflected simply as a data
> property directly, and hence has to be read and written through
> immediately. The most effective way to optimize that is implementing
> the global object using internal accessors (that look like data
> properties). So in terms of _efficient_ implementation of the
> toplevel, both approaches are equivalent, and as an implementer, I
> don't care much either way.
>
> Also, note that for ES6 modules, we will have to optimize these kinds
> of accessors anyway.
>
> One thing I noticed with your comments is that they consider the
> global scope almost exclusively from the object perspective, i.e., as
> special syntax for putting properties on the global object. But first
> and foremost, it is a scope, so coherent scoping semantics is at least
> as important. If it is incoherent with other scopes, that is
> confusing, and a serious refactoring hazards. With static scoping,
>
> /Andreas
> _______________________________________________
> es-discuss mailing list
> es-discuss at mozilla.org
> https://mail.mozilla.org/listinfo/es-discuss
>
> Gavin Barraclough <mailto:barraclough at apple.com>
> January 22, 2012 2:46 PM
> On Jan 20, 2012, at 4:23 PM, Allen Wirfs-Brock wrote:
>
> I'm strongly inclined to agree with this sentiment – engine 
> implementors might choose internally to utilize accessors in their 
> implementations, but from a user perspective it seems to make much 
> more sense that these remain as regular data properties. It is worth 
> bearing in mind that 'const' is already available outside of strict 
> mode and we need to be considerate of any breaking changes we make to 
> existing semantics. (We could define incompatible semantics for 
> strict-mode only, but this would likely lead to a confusing divergence 
> in semantics for users – it could be horribly confusing if const 
> variables in non-strict code were to be implemented as data properties 
> whilst those in strict code were defined as accessor properties).
>
> alert('value' in Object.getOwnPropertyDescriptor(this, 'x'));
> alert(Object.getOwnPropertyDescriptor(this, 'x').writable === true);
>
> alert(Object.getOwnPropertyDescriptor(this, 'x').value);
> x = 1;
> alert(Object.getOwnPropertyDescriptor(this, 'x').value);
> const x = 2;
> alert(Object.getOwnPropertyDescriptor(this, 'x').value);
> x = 3;
> alert(Object.getOwnPropertyDescriptor(this, 'x').value);
>
> This script tests the presence and writability of a property on the 
> global object, and the effect of attempting to assign to it in 
> non-strict code. It demonstrates a non-const var behaving as a regular 
> non-writable property in non-strict code.
>
> On FireFox the above script outputs "true, false, undefined, 
> undefined, 2, 2", which to my mind makes a lot of sense. It perfectly 
> matches, as far as one can test, any other regular non writable 
> property that you could create on an object (of course there it not 
> normally the opportunity to split creation and initialization of a 
> non-writable property).
>
> This behaviour is also sensibly consistent with that for let and var. 
> Running the above script, changing 'const' to 'var' outputs "true, 
> true, undefined, 1, 2, 3" (indicating the property is writable, and 
> all assignments succeed), and for 'let' currently also outputs "true, 
> true, undefined, 1, 2, 3" on FireFox. To my mind this behaviour should 
> probably change slightly. With a temporal dead zone implemented I 
> would expect an attempt to [[DefineOwnProperty]] to an uninitialized 
> value to be rejected (though I would not expect an exception to be 
> thrown from non-strict code) so I would expect the output for 'let' to 
> be "true, true, undefined, undefined, 2, 3" (the assignment of 'a' 
> silently fails leaving the value unchanged).
>
> I would suggest that specifying this as a piece of additional hidden 
> internal state on a data descriptor (rejecting attempts to define or 
> access the value prior to initialization, throwing in strict-mode & 
> silently ignoring in non-strict) makes most sense on a number of grounds:
>
> – compatibility with existing non-strict const implementations.
> – consistency with appearance of var properties on the global object.
> – consistency with appearance of regular non-writable properties on 
> other objects.
> – consistency with behaviour of access to regular non-writable 
> properties on other objects.
> – data properties typically have higher performance in 
> implementations, specifying all global let & const properties to be 
> accessors may introduce unnecessary overhead.
>
> cheers,
> G.
>
> _______________________________________________
> es-discuss mailing list
> es-discuss at mozilla.org
> https://mail.mozilla.org/listinfo/es-discuss
> Allen Wirfs-Brock <mailto:allen at wirfs-brock.com>
> January 20, 2012 4:23 PM
>
> On Jan 20, 2012, at 5:55 AM, Andreas Rossberg wrote:
>
>> From the TC39 notes:
>>> DaveH: What if there are duplicate top-level bindings?
>>> MarkM: Top-level let should behave as much as possible the same as
>>> concatenated scripts.
>>
>> It's my claim that it is impossible to reconcile all of the following
>> three goals in a way that is even remotely sane:
>>
>> 1. Top-level bindings are aliased as data properties of the global 
>> object.
>> 2. Top-level bindings allow redefinition of existing properties of the
>> global object.
>
> the rules are more complex than this. see 
> https://bugs.ecmascript.org/show_bug.cgi?id=78 There are important 
> distinctions between own and inherited properties of the global object 
> and properties created using var/function declarations (all we have in 
> ES5) and arbitrary properties directly created by the user or host.
>
>> 3. Top-level let/const have a temporal dead-zone.
>>
>> Abandoning (3) essentially means abandoning a reasonable "const"
>> semantics for the toplevel (and future hostile to guards), so we don't
>> want to do that. (For V8, we had long struggles with defining and
>> implementing a const for the (current notion of) top-level that
>> actually makes any sense. We failed.)
>
> Temporal dead-zone initialization tracking requires extra state and 
> checking (except for the common cases within functions where it can be 
> statically determined that there are no possible references within the 
> dead zone). This additional requirement is reflected in the 
> specification in the ES5 definition of the CreateImmutableBinding and 
> InitializeImmutableBinding environment record methods. In ES5 this 
> mechanism (and state) was only needed within 
> DeclarativeEnviornmentRecords for (the rarely used) immutable 
> bindings. For the ES6 spec. this has been generalize to also apply to 
> ObjectEnvironmentRecords and to track initialization of both mutable 
> and immutable bindings.
>
> The specification doesn't address how an implementation might 
> represent that state but it needs to be somewhere. Closure capturing 
> the state in getter/setter functions may be one way to do this for the 
> global scope. I imagine that there are other ways. For example, global 
> object properties could potentially have an implementation specific 
> "not yet initialized" bit associated with properties on the global 
> object. Or you might "shadow" the entire global object with a special 
> kind of environment record that that censors access to instantiated 
> but initialized global bindings (add entry to the censor environment 
> when the binding is instantiated, only create a property in the global 
> object when it is initialized).
>
> However, I agree that there are still significant issues with 
> rationally integrating the semantics of global object property access 
> semantics and the ES global declaration semantics. Both for ES5 and ES6.
>
>>
>> Hence, I would like to suggest another, more moderate idea for a
>> global scope reform: treat the global object similar to a module
>> instance objects. That is:
>>
>> - Top-level bindings (other than var) are reflected as accessor
>> properties on the global object.
>> - They are defined on the global object before the script starts 
>> executing.
>> - Their getters/setters simply delegate to the bound variables.
>
> whose access presumably perform the necessary checks to enforce the 
> temporal deadzone.
>
>> - For const bindings, there are no setters.
>> - These properties are non-configurable.
>
> It is already the case (for ES5) that all properties created for 
> global declarations are non-configurable (unless created by an eval).
>
>> - Unlike a module instance object, the global object is extensible
>> (multiple scripts!).
>>
>> This disallows lexical bindings to redefine existing non-configurable
>> properties of the global object (including ones reflecting bindings
>> from earlier scripts), which is checked before the script starts
>> executing. Likewise, let/const-defined properties cannot later be
>> deleted from the global object.
>
> I'm not yet convinced that you need to publicly expose such global 
> object properties accessor properties. I think it is possible to hide 
> the mechanisms.
>
>>
>> Apart from that, not much should change in practical terms. You should
>> pretty much be able to replace var with let. At the same time, static
>> scoping actually is maintained, and there is no issue with temporal
>> dead zones. Moreover, concatenating scripts would not affect the
>> meaning or validity of bindings, AFAICS.
>
> Independent of implementation schemes, I think we need to decide on 
> the meaning of the following independent scripts (in different documents)
>
>
> <script>
> "do not use strict";
> //window.foo does not exist at this point.
> foo = 10
> const foo=5; //or let
> print(foo);
> </script>
>
> <script>
> //window.foo does not exist at this point.
> window.foo = 10
> const foo=5; //or let
> print(foo);
> </script>
>
> <script>
> //window.foo does not exist at this point.
> print(foo);
> const foo=5; //or let
> print(foo);
> </script>
>
> ...
>
>>
>> Regarding scope pollution, I think this is best solved by making the
>> global object be a fresh empty object that has the object containing
>> all primordials as its prototype. Overriding is allowed according to
>> the standard defineProperty semantics. No special rules necessary.
>
> In that case, why is it an object at all? Why not simply a 
> DeclarativeEnvironmentRecord in an environment whose outer is a 
> GlobalEnvironmentRecord whose backing object is the global object? I'm 
> actually think along some similar lines but I think this direction 
> raises interesting questions, going forward, about about some current 
> fundamental assumptions:
>
> What is the role of the global object?
> Must all ES global declarations reify as properties on the global object?
> If not, which global declarations must reify as such? (what are the 
> actual web dependencies).
> Must all built-in globally accessible bindings reify as global object 
> properties?
>
> I have some ideas. I'll see if they hold together as I try to fill 
> them out.
>
> Allen
>
>
>
>
>
>>
>> /Andreas
>> _______________________________________________
>> es-discuss mailing list
>> es-discuss at mozilla.org <mailto:es-discuss at mozilla.org>
>> https://mail.mozilla.org/listinfo/es-discuss
>>
>
> _______________________________________________
> es-discuss mailing list
> es-discuss at mozilla.org
> https://mail.mozilla.org/listinfo/es-discuss


More information about the es-discuss mailing list