/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