Andreas Rossberg <mailto:[email protected]>
January 23, 2012 10:15 AM
On 23 January 2012 18:39, Brendan Eich<[email protected]>  wrote:
Andreas Rossberg<mailto:[email protected]>
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
[email protected]
https://mail.mozilla.org/listinfo/es-discuss

Brendan Eich <mailto:[email protected]>
January 23, 2012 9:39 AM
Andreas Rossberg <mailto:[email protected]>
January 23, 2012 5:54 AM
On 22 January 2012 23:46, Gavin Barraclough<[email protected]> 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:[email protected]>
January 23, 2012 5:54 AM
On 22 January 2012 23:46, Gavin Barraclough<[email protected]>  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
[email protected]
https://mail.mozilla.org/listinfo/es-discuss

Gavin Barraclough <mailto:[email protected]>
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
[email protected]
https://mail.mozilla.org/listinfo/es-discuss
Allen Wirfs-Brock <mailto:[email protected]>
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
[email protected] <mailto:[email protected]>
https://mail.mozilla.org/listinfo/es-discuss


_______________________________________________
es-discuss mailing list
[email protected]
https://mail.mozilla.org/listinfo/es-discuss
_______________________________________________
es-discuss mailing list
[email protected]
https://mail.mozilla.org/listinfo/es-discuss

Reply via email to