ES6 doesn't need opt-in

# David Herman (12 years ago)

Happy new year, everyone. I've got some good news.

There's been grumbling lately about the ES6 version opt-in [1]. The detractors are not right about everything, but it's true that both the MIME type opt-in:

<script type="application/ecmascript;version=6">
    ...
</script>

and the pragma opt-in:

use version 6;

are painful -- ugly, verbose, and a permanent tax for opting to use the new semantics.

We can do better, and the way we can do it is with modules. We can introduce module syntax in a backwards-compatible way, and use them as the implicit opt-in for any new semantics [2].

And the benefit we get is just one JavaScript.

Backwards-compatible syntax

Both use and module can be contextual keywords, with a restricted production that disallows a newline after the keyword. To avoid ambiguity with contextual infix operators like is and isnt [3], such as:

module is {}

we can resolve in favor of the declaration form in statement context, and in any other expression contexts module is just an ordinary identifier.

This is totally backwards-compatible: all the new forms would have been parse errors in ES5:

module foo { }
module bar {
}
module {
}

and in all cases that ASI accepts in ES5, it continues to parse the same, i.e., not as a module:

module // semicolon inserted
foo    // semicolon inserted

Modules as the opt-in for new semantics

Any ES6 feature that breaks web-compatibility can be made available only within module code. For example, if let as a keyword breaks the web, then in global code we treat it as an identifier, but we treat it as a keyword once you're inside a module. Similarly, the proper but backwards-incompatible scoping semantics for block-local functions would only be enabled within module code.

We could do the same for yield, but since it's only meaningful within function*, we should allow generator functions outside of modules as well. I'm not sure whether we can/should reserve yield as a keyword in global code.

Each new feature can be considered independently. Anything that is only allowed within modules becomes a carrot to lead programmers to the improved semantics, but whenever we can compatibly make something available to global code, we should do so. No point needlessly depriving programmers of ES6 goodies such as destructuring or spread/rest.

What about eliminating the window from the scope chain?

Short answer: giving up.

You can still do it with a custom loader, and I still want to push on new HTML <meta> conveniences for specifying a custom loader to use for the remainder of the program. In that context you could create a loader with a nice, clean global scope.

But then what about static scoping?

Compile-time checking of variables is a really important goal, and we don't have to abandon it. We just do the checking only within module code, relative to the current contents of the global object. This means it's still technically possible for globals to disappear at runtime (and consequently throw errors).

But this doesn't bother me. First of all, variable errors due to dynamic deletion of globals are not the common case. Second, you can avoid globals entirely by strictly importing from modules. Finally, you can easily create loaders with frozen global objects. An environment like SES might choose to do this. But we retain compatibility in the default browser context.

If you ask me, this compromise is totally worth it.

Anonymous modules as a simple way of opting in

A common pattern on the web is to protect yourself against creating unnecessary globals is to wrap top-level code in an IIFE:

(function() {
    /* program goes here */
})();

By allowing anonymous modules, we simultaneously make this more idiomatic and implicitly opt in to all the goodies of ES6:

module {
    /* program goes here */
}

Kills two birds with one stone!

Avoiding rightward drift

Still, whether you use an IIFE or an anonymous module, the extra rightward drift (requiring an initial level of indentation around the entire program body) is annoying. A simple pragma takes care of that:

// equivalent to wrapping program in module { ... }
use module;

This is not strictly necessary but avoiding an extra level indentation in every single program is a big win when you multiply it over every program ever written, and some editors will fight you if you try not to indent within curlies. But notice that even though this is a kind of opt-in pragma, it still is strictly less code than you have to write today with an IIFE -- kills two birds with a smaller stone than ES5 requires to kill one.

Summing up

We can make ES6 100% backwards-compatible, using modules to opt in to backwards-incompatible changes. It replaces the notion of a new language "mode" with the simple syntactic characteristic of whether the code is in a module. And you can now mix old code and new code freely, even within the same <script> tag or source file.

One final note: the "...;version=6" MIME-type could be used to hide ES6 scripts from downrev browsers. So it may still have a place, but not for opting in to new semantics.

Dave

[1] plus.google.com/112284435661490019880/posts/6W7ErmRC1XN

[2] We can't be future-compatible, as some have hoped to accomplish with HTML, except maybe with macros. We'll get there if/when we can.

[3] harmony:egal#is_and_isnt_operators

# Oliver Hunt (12 years ago)

On Dec 31, 2011, at 5:46 PM, David Herman wrote:

Happy new year, everyone. I've got some good news.

There's been grumbling lately about the ES6 version opt-in [1]. The detractors are not right about everything, but it's true that both the MIME type opt-in:

<script type="application/ecmascript;version=6"> ... </script>

and the pragma opt-in:

use version 6;

are painful -- ugly, verbose, and a permanent tax for opting to use the new semantics.

We can do better, and the way we can do it is with modules. We can introduce module syntax in a backwards-compatible way, and use them as the implicit opt-in for any new semantics [2].

And the benefit we get is just one JavaScript.

Backwards-compatible syntax

Both use and module can be contextual keywords, with a restricted production that disallows a newline after the keyword. To avoid ambiguity with contextual infix operators like is and isnt [3], such as:

module is {}

we can resolve in favor of the declaration form in statement context, and in any other expression contexts module is just an ordinary identifier.

This is totally backwards-compatible: all the new forms would have been parse errors in ES5:

module foo { } module bar { } module { }

and in all cases that ASI accepts in ES5, it continues to parse the same, i.e., not as a module:

module // semicolon inserted foo // semicolon inserted

Modules as the opt-in for new semantics

Any ES6 feature that breaks web-compatibility can be made available only within module code. For example, if let as a keyword breaks the web, then in global code we treat it as an identifier, but we treat it as a keyword once you're inside a module. Similarly, the proper but backwards-incompatible scoping semantics for block-local functions would only be enabled within module code.

We could do the same for yield, but since it's only meaningful within function*, we should allow generator functions outside of modules as well. I'm not sure whether we can/should reserve yield as a keyword in global code.

Each new feature can be considered independently. Anything that is only allowed within modules becomes a carrot to lead programmers to the improved semantics, but whenever we can compatibly make something available to global code, we should do so. No point needlessly depriving programmers of ES6 goodies such as destructuring or spread/rest.

I'm willing to have a shot at making JSC treat a bunch of the current "reserved only in strict mode" keywords be reserved globally and see what breaks in the nightlies. I think yield is a special case in that it only applies inside generators anyway, so making it "contextual" in generators seems obvious.

What about eliminating the window from the scope chain?

Short answer: giving up.

I think we can drop the global object from the scope chain in ES. In the context of the browser we need to come up with a way to trigger it.

Summing up

We can make ES6 100% backwards-compatible, using modules to opt in to backwards-incompatible changes. It replaces the notion of a new language "mode" with the simple syntactic characteristic of whether the code is in a module. And you can now mix old code and new code freely, even within the same <script> tag or source file.

Someone brought up the typeof semantic change as concern :-/

# David Herman (12 years ago)

On Dec 31, 2011, at 6:08 PM, Oliver Hunt wrote:

What about eliminating the window from the scope chain?

Short answer: giving up.

I think we can drop the global object from the scope chain in ES. In the context of the browser we need to come up with a way to trigger it.

Maybe. I don't think it's as important. You can choose to use no globals whatsoever if you want once we have the stdlib available via standard "@" URL's.

If we can find a simple way to opt out of the global object that isn't a burden, I'm for it. But I suspect if it's too much work, it's just easier... to not use the global object. :)

Someone brought up the typeof semantic change as concern :-/

I'm not concerned. It's technically doable, in that the change would only be within module code. But I think changing the semantics of typeof in only part of JS is a bad idea anyway. It's not enough win to pay for itself. And I think it's pretty clear we can't change it everywhere without breaking the web.

# Brendan Eich (12 years ago)

From: "David Herman" <dherman at mozilla.com>

On Dec 31, 2011, at 6:08 PM, Oliver Hunt wrote:

What about eliminating the window from the scope chain?

Short answer: giving up.

I think we can drop the global object from the scope chain in ES. In the context of the browser we need to come up with a way to trigger it.

Maybe. I don't think it's as important. You can choose to use no globals whatsoever if you want once we have the stdlib available via standard "@" URL's.

Nit-fun: MRLs -- @ cannot begin a URL ;-). We're supposed to say URI (TBL and others pronounce it like "Yuri"), so maybe MRI. Magnetic Resonance Imaging...

If we can find a simple way to opt out of the global object that isn't a burden, I'm for it. But I suspect if it's too much work, it's just easier... to not use the global object. :)

Right. But I doubt we'll find a simple enough way to opt out of the global object that's also compatible enough to migrate existing code into. That migration hazard, the first finger of fate when we said we intended to remove the global object as top scope, is huge. Lots of code on the web detects properties of window or |this| at top level while also declaring the same name via an uninitialized 'var'.

Someone brought up the typeof semantic change as concern :-/

I'm not concerned. It's technically doable, in that the change would only be within module code. But I think changing the semantics of typeof in only part of JS is a bad idea anyway. It's not enough win to pay for itself. And I think it's pretty clear we can't change it everywhere without breaking the web.

One counter-argument:

  1. We want sane isObject and isNull predicates, ideally using typeof. Lack of them continues to bite people, as the web contains code that wrongly assumes typeof x == "object" => x.foo won't throw on null x.

  2. We want typeof to improve when/if value types appear, and already IE extends typeof's result set, so people have to be careful not to write exhaustive typeof case analysis code.

  3. Therefore, to make JS in the future (down the road and around the bend far enough that we can't see all the way -- and if we get there, the look back won't see bad old typeof null == "object") saner in its typeof semantics, we ought to bend a finger of fate on the runtime incompatibility of typeof null == "null".

I'm not going to die on this hill but I think it's worth a bit more thought, at least till midnight ;-).

# Axel Rauschmayer (12 years ago)

Each new feature can be considered independently. Anything that is only allowed within modules becomes a carrot to lead programmers to the improved semantics, but whenever we can compatibly make something available to global code, we should do so. No point needlessly depriving programmers of ES6 goodies such as destructuring or spread/rest.

I’m not entirely sure, but introducing two new dialects (ES5.5 and ES6, if you will) at the same time seems problematic. It might be easier to stick to the simpler rule of “global = ES5”.

Apart from that: sold.

# Axel Rauschmayer (12 years ago)
  1. We want sane isObject and isNull predicates, ideally using typeof. Lack of them continues to bite people, as the web contains code that wrongly assumes typeof x == "object" => x.foo won't throw on null x.

What are the use cases for typeof? Offhand, I see five and for most of them, other solutions seem to be better.

  1. Checking whether a variable has been declared. Problematic: verbose and conflated with checking for a declared variable having the value undefined. Better: a dedicated operator or predicate for performing this check.

  2. Checking that a value is neither null nor undefined. Problematic: can’t be done via only typeof currently. Better: a predicate for performing this check. This use case will become less important with default parameter values.

  3. Distinguishing between objects and primitives. Problematic: Made more difficult by typeof null === "object" and typeof function () {} === "function". Better: predicates such as isObject() and isPrimitive()

  4. Determining the type of a primitive value. Better: typeof is OK here, but changing it so that typeof null === "null" would help.

  5. Determining the type of a value (primitive or otherwise). Better: I would want a function, e.g. getTypeName() that works like (null-enabled) typeof for primitives and returns the value of the [[Class]] property for objects.

Everything except #1 can be easily implemented as functions (and be brought to ES5 via shims). A function such the #5 getTypeName() could take care of use case #4, as well.

Ideas for getTypeName(): www.2ality.com/2011/11/improving-typeof.html

# Brendan Eich (12 years ago)

There is no 5.5 -- new features can be used from unversioned script, but those that break backward compatibility (old code running in new browser) must be inside a new special form that expresses explicit opt-in. This means

yield reserved in function* let reserved in module body (however declared) free variable error checking in module body completion reform in module body typeof null reform in module body (debatable)

Global code still can use new features such as module declarations. That means your "global = ES5" does not work. Do not linearize onto a version number line.

# John J Barton (12 years ago)

On Sat, Dec 31, 2011 at 7:53 PM, Axel Rauschmayer <axel at rauschma.de> wrote:

  1. We want sane isObject and isNull predicates, ideally using typeof. Lack of them continues to bite people, as the web contains code that wrongly assumes typeof x == "object" => x.foo won't throw on null x.

What are the use cases for typeof? Offhand, I see five and for most of them, other solutions seem to be better.

In my experience the major use of typeof is to implement dynamic function overloading. That is, a function that is flexible in the arguments it takes, such that one logical name maps to multiple use cases.

  1. Checking whether a variable has been declared. Problematic: verbose and conflated with checking for a declared variable having the value undefined. Better: a dedicated operator or predicate for performing this check.

Sorry, I don't think anyone checks if a is variable declared. You just look at the source code.

  1. Checking that a value is neither null nor undefined. Problematic: can’t be done via only typeof currently. Better: a predicate for performing this check. This use case will become less important with default parameter values.

if(!name) {}

  1. Distinguishing between objects and primitives. Problematic: Made more difficult by typeof null === "object" and typeof function () {} === "function". Better: predicates such as isObject() and isPrimitive()

Mostly we are checking string, array, function or object. Only Java devs pass/return |null| so that case is not that big of a deal. Or another way to look at it: if you use null, it's your own little signal, so enjoy.

Array is the famously difficult case.

  1. Determining the type of a primitive value. Better: typeof is OK here, but changing it so that typeof null === "null" would help.

  2. Determining the type of a value (primitive or otherwise). Better: I would want a function, e.g. getTypeName() that works like (null-enabled) typeof for primitives and returns the value of the [[Class]] property for objects.

This would be great!

# David Bruant (12 years ago)

Le 01/01/2012 06:12, John J Barton a écrit :

On Sat, Dec 31, 2011 at 7:53 PM, Axel Rauschmayer <axel at rauschma.de <mailto:axel at rauschma.de>> wrote:

1. Checking whether a variable has been declared.
    Problematic: verbose and conflated with checking for a
declared variable having the value `undefined`.
    Better: a dedicated operator or predicate for performing this
check.

Sorry, I don't think anyone checks if a is variable declared. You just look at the source code.

I have read code where people checked variable existence in global code. Typeof prevents a ReferenceError if the variable is not declared. Having been used to do it in global code (where you can't always "read the code"), or just reading it somewhere, people reproduce within a function.

I agree that there are a lot of bad practices in here, but some people do do it. Moreover, the concept of static scoping and "just look at the code to see if a variable is declared" is not that obvious for newcomers to the language or newcomers to programming.

# Dean Landolt (12 years ago)

On Sun, Jan 1, 2012 at 12:12 AM, John J Barton <johnjbarton at johnjbarton.com>wrote:

On Sat, Dec 31, 2011 at 7:53 PM, Axel Rauschmayer <axel at rauschma.de>wrote:

  1. We want sane isObject and isNull predicates, ideally using typeof. Lack of them continues to bite people, as the web contains code that wrongly assumes typeof x == "object" => x.foo won't throw on null x.

What are the use cases for typeof? Offhand, I see five and for most of them, other solutions seem to be better.

In my experience the major use of typeof is to implement dynamic function overloading. That is, a function that is flexible in the arguments it takes, such that one logical name maps to multiple use cases.

  1. Checking whether a variable has been declared. Problematic: verbose and conflated with checking for a declared variable having the value undefined. Better: a dedicated operator or predicate for performing this check.

Sorry, I don't think anyone checks if a is variable declared. You just look at the source code.

Tell that to people writing feature detection code :)

  1. Checking that a value is neither null nor undefined.
Problematic: can’t be done via only typeof currently.
Better: a predicate for performing this check. This use case will

become less important with default parameter values.

if(!name) {}

And if name is 0? Or ""? Whoops. Javascript's coercion is pretty winful here (in spite of what jslint might say): if (name == null) is more targeted, just catching the name === null and name === void 0 cases.

  1. Distinguishing between objects and primitives.
Problematic: Made more difficult by typeof null === "object" and

typeof function () {} === "function". Better: predicates such as isObject() and isPrimitive()

Mostly we are checking string, array, function or object. Only Java devs pass/return |null| so that case is not that big of a deal. Or another way to look at it: if you use null, it's your own little signal, so enjoy.

Again, some people write generic code. Sure, those people should be more aware of the jswtfs but we can't just pretend they're not there.

Array is the famously difficult case.

Until es5 gave us Array.isArray. But yes, array is another branch in the object-detection code. And like array, everyone has slightly different object detection rules. It would be a gift if the language were to fix this.

  1. Determining the type of a primitive value.
Better: typeof is OK here, but changing it so that typeof null ===

"null" would help.

  1. Determining the type of a value (primitive or otherwise). Better: I would want a function, e.g. getTypeName() that works like (null-enabled) typeof for primitives and returns the value of the [[Class]] property for objects.

This would be great!

Agreed. ISTM the best way to reform typeof may be to just punt on it entirely. This would be a lot easier to do if the language gave us a better tool for all of these use cases OOTB. And as Axel points out, modules make it a lot easier for the language to give us these tools.

# John J Barton (12 years ago)

On Sun, Jan 1, 2012 at 5:12 AM, David Bruant <bruant.d at gmail.com> wrote:

Le 01/01/2012 06:12, John J Barton a écrit :

On Sat, Dec 31, 2011 at 7:53 PM, Axel Rauschmayer <axel at rauschma.de>wrote:

  1. Checking whether a variable has been declared. Problematic: verbose and conflated with checking for a declared variable having the value undefined. Better: a dedicated operator or predicate for performing this check.

Sorry, I don't think anyone checks if a is variable declared. You just look at the source code.

I have read code where people checked variable existence in global code. Typeof prevents a ReferenceError if the variable is not declared. Having been used to do it in global code (where you can't always "read the code"), or just reading it somewhere, people reproduce within a function.

Maybe I am confused. Here is what I was imagining: var w = 1; // w is declared and defined. typeof gives 'number' var x; // x is declared, but is undefined. typeof gives 'undefined' y = 4; // y is not declared, is a global. typeof gives 'number' // z is not declared typeof gives 'undefined.

So how can I check to see that y and z are not declared?

In: var b; if (typeof x !== 'undefined') b = x

I don't get a ReferenceError. What I am I missing?

I agree that there are a lot of bad practices in here, but some people do do it. Moreover, the concept of static scoping and "just look at the code to see if a variable is declared" is not that obvious for newcomers to the language or newcomers to programming.

I guess newcomers won't understand typeof either ;-).

jjb

# John J Barton (12 years ago)

On Sun, Jan 1, 2012 at 6:32 AM, Dean Landolt <dean at deanlandolt.com> wrote:

On Sun, Jan 1, 2012 at 12:12 AM, John J Barton < johnjbarton at johnjbarton.com> wrote:

On Sat, Dec 31, 2011 at 7:53 PM, Axel Rauschmayer <axel at rauschma.de>wrote:

  1. We want sane isObject and isNull predicates, ideally using typeof. Lack of them continues to bite people, as the web contains code that wrongly assumes typeof x == "object" => x.foo won't throw on null x.

What are the use cases for typeof? Offhand, I see five and for most of them, other solutions seem to be better.

In my experience the major use of typeof is to implement dynamic function overloading. That is, a function that is flexible in the arguments it takes, such that one logical name maps to multiple use cases.

  1. Checking whether a variable has been declared. Problematic: verbose and conflated with checking for a declared variable having the value undefined. Better: a dedicated operator or predicate for performing this check.

Sorry, I don't think anyone checks if a is variable declared. You just look at the source code.

Tell that to people writing feature detection code :)

Sorry I don't understand what you mean here. I thought feature detection relied on testing for the existence of certain object properties and functions.

  1. Checking that a value is neither null nor undefined.
Problematic: can’t be done via only typeof currently.
Better: a predicate for performing this check. This use case will

become less important with default parameter values.

if(!name) {}

And if name is 0? Or ""? Whoops. Javascript's coercion is pretty winful here (in spite of what jslint might say): if (name == null) is more targeted, just catching the name === null and name === void 0 cases.

I was challenging Axel's characterization of how JS devs use typeof, since it leads him to conclude that we need to add a bunch of new language features. In code I see, devs don't use typeof for checking a value is null or undefined, so don't base new language features on this use case.

I agree that the common practice of testing if(!name) has pitfalls. But doesn't the common practice suggest that more elaborate features will not help? We need something as simple as if (!name).

jjb

# Axel Rauschmayer (12 years ago)

Maybe I am confused. Here is what I was imagining: var w = 1; // w is declared and defined. typeof gives 'number' var x; // x is declared, but is undefined. typeof gives 'undefined' y = 4; // y is not declared, is a global. typeof gives 'number' // z is not declared typeof gives 'undefined.

So how can I check to see that y and z are not declared?

In: var b; if (typeof x !== 'undefined') b = x

I don't get a ReferenceError. What I am I missing?

Not a frequent use case (again, even less frequent with modules in ES6), but it exists: Boilerplate to Node.js-enable an asynchronous module definition.

({ define: typeof define === "function"
    ? define  // browser
    : function(F) { F(require,exports,module) } }).  // Node.js
define(function (require, exports, module) {
    // Node.js module code goes here
});

Alternative: wrap a function around define(), make define a parameter, perform the (same!) check when immediately-invoking the function.

# John J Barton (12 years ago)

On Sun, Jan 1, 2012 at 9:09 AM, Axel Rauschmayer <axel at rauschma.de> wrote:

Maybe I am confused. Here is what I was imagining: var w = 1; // w is declared and defined. typeof gives 'number' var x; // x is declared, but is undefined. typeof gives 'undefined' y = 4; // y is not declared, is a global. typeof gives 'number' // z is not declared typeof gives 'undefined.

So how can I check to see that y and z are not declared?

In: var b; if (typeof x !== 'undefined') b = x

I don't get a ReferenceError. What I am I missing?

Not a frequent use case (again, even less frequent with modules in ES6), but it exists: Boilerplate to Node.js-enable an asynchronous module definition.

({ define: typeof define === "function" ? define // browser : function(F) { F(require,exports,module) } }). // Node.js define(function (require, exports, module) { // Node.js module code goes here });

Ok, thanks for the example. Here the test typeof define === 'function' is equivalent to typeof window.define === 'function' Is this testing whether 'define' is "declared"? I thought that 'window' is built-in so window.foo can either have a value or be 'undefined', but window.foo can not be declared.

Anyway, if this example illustrates your use case, then it is the important case in my opinion (dynamic function overloading, supporting multiple callers). (And I don't see how any dedicated operator can help.)

jjb

# Axel Rauschmayer (12 years ago)

I was challenging Axel's characterization of how JS devs use typeof, since it leads him to conclude that we need to add a bunch of new language features. In code I see, devs don't use typeof for checking a value is null or undefined, so don't base new language features on this use case.

My intention was to list all use cases, to see where typeof could possibly be useful (i.e., to assert its merits). Should it be better to replace it with something else, then there might not be a need to fix typeof null.

I agree that the common practice of testing if(!name) has pitfalls. But doesn't the common practice suggest that more elaborate features will not help? We need something as simple as if (!name).

Default parameter values should mostly take care of this. The default operator also looks useful (if extended to include null): strawman:default_operator

It might make sense to add ?? as a unary operator: var myvar; console.log(?? myvar); // false console.log(?? globalUnknown); // false var obj = {}; console.log(?? obj.prop); // false

If something non-grawlixy is preferred, one could give it a name such as exists. Furthermore, it might be more consistent with value ?? default to make ?? a postfix operator, but I can’t judge the grammatical implications of that.

# Axel Rauschmayer (12 years ago)

({ define: typeof define === "function" ? define // browser : function(F) { F(require,exports,module) } }). // Node.js define(function (require, exports, module) { // Node.js module code goes here });

Ok, thanks for the example. Here the test typeof define === 'function' is equivalent to typeof window.define === 'function' Is this testing whether 'define' is "declared"?

Yes.

I thought that 'window' is built-in so window.foo can either have a value or be 'undefined', but window.foo can not be declared.

Note: window does not exist on Node.js (and possibly other non-browser environments).

Anyway, if this example illustrates your use case, then it is the important case in my opinion (dynamic function overloading, supporting multiple callers). (And I don't see how any dedicated operator can help.)

If there was an operator exists, the following two expressions would be equivalent. I prefer the latter expression, because it is more explicit.

 typeof define === "function"
 exists define && define instanceof Function

The exists operator would work as follows (slightly edited from a previous email):

console.log(exists globalUnknown); // false

// undefined and null in several variations

var myvar;
console.log(exists myvar); // false

console.log(exists undefined); // false
console.log(exists null); // false

var obj = {};
console.log(exists obj.prop); // false
# Rick Waldron (12 years ago)

On Sun, Jan 1, 2012 at 12:45 PM, Axel Rauschmayer <axel at rauschma.de> wrote:

({ define: typeof define === "function"
   ? define  // browser
   : function(F) { F(require,exports,module) } }).  // Node.js

define(function (require, exports, module) { // Node.js module code goes here });

Ok, thanks for the example. Here the test typeof define === 'function' is equivalent to typeof window.define === 'function' Is this testing whether 'define' is "declared"?

Yes.

I thought that 'window' is built-in so window.foo can either have a value or be 'undefined', but window.foo can not be declared.

Note: window does not exist on Node.js (and possibly other non-browser environments).

Anyway, if this example illustrates your use case, then it is the important case in my opinion (dynamic function overloading, supporting multiple callers). (And I don't see how any dedicated operator can help.)

If there was an operator exists, the following two expressions would be equivalent. I prefer the latter expression, because it is more explicit.

 typeof define === "function"
 exists define && define instanceof Function

The exists operator would work as follows (slightly edited from a previous email):

console.log(exists globalUnknown); // false

Same as... ( "globalUnknown" in this ) // anywhere ( "globalUnknown" in window ) // browser ( "globalUnknown" in global ) // node ( "globalUnknown" in self ) // workerglobalscope

But this is so common that it might warrant an upgrade

// undefined and null in several variations

var myvar;
console.log(exists myvar); // false

"exists" is misleading here, "myvar" does exist - you just initialized it, but did not assign a value. If I've understood 12.2 correctly, "myvar" definitely "exists"

console.log(exists undefined); // false
console.log(exists null); // false

Same argument as given for "myvar"

var obj = {};
console.log(exists obj.prop); // false

This makes complete sense, but still same as my first example

( "prop" in obj )

# Axel Rauschmayer (12 years ago)

Same as... ( "globalUnknown" in this ) // anywhere
( "globalUnknown" in window ) // browser ( "globalUnknown" in global ) // node ( "globalUnknown" in self ) // workerglobalscope

But this is so common that it might warrant an upgrade

I’m not 100% sure but I was under the impression that scopes-as-objects will go away in the long run. Then you wouldn’t be able to use the in operator, any more. The first expression is nice in that it works on both browsers and Node.js.

// undefined and null in several variations

var myvar;
console.log(exists myvar); // false

"exists" is misleading here, "myvar" does exist - you just initialized it, but did not assign a value. If I've understood 12.2 correctly, "myvar" definitely "exists"

Suggestions for better names welcome! isdefined ?

var obj = {};
console.log(exists obj.prop); // false

This makes complete sense, but still same as my first example

( "prop" in obj )

It’s mainly there for reasons of symmetry to me, a variable having been declared is the same as a property existing. JavaScript differs from most languages in that it doesn’t report an error when a non-existant property is accessed.

# John J Barton (12 years ago)

On Sun, Jan 1, 2012 at 9:45 AM, Axel Rauschmayer <axel at rauschma.de> wrote:

({ define: typeof define === "function"

   ? define  // browser
   : function(F) { F(require,exports,module) } }).  // Node.js

define(function (require, exports, module) { // Node.js module code goes here });

Ok, thanks for the example. Here the test typeof define === 'function' is equivalent to typeof window.define === 'function' Is this testing whether 'define' is "declared"?

Yes.

I thought that 'window' is built-in so window.foo can either have a value or be 'undefined', but window.foo can not be declared.

Note: window does not exist on Node.js (and possibly other non-browser environments).

Sure, but in that case the test typeof define === 'function' tests the value that the variable 'define' references. It does not test if "define' has been declared.

Anyway, if this example illustrates your use case, then it is the important case in my opinion (dynamic function overloading, supporting multiple callers). (And I don't see how any dedicated operator can help.)

If there was an operator exists, the following two expressions would be equivalent. I prefer the latter expression, because it is more explicit.

 typeof define === "function"
 exists define && define instanceof Function

Ok now I understand David's comment about ReferenceError. If you try to use instanceof for these kinds of tests, you have to prefix every use with a test it ensure that the variable exists.

I prefer the former expression, because it is clearer.

The exists operator would work as follows (slightly edited from a previous email):

console.log(exists globalUnknown); // false

// undefined and null in several variations

var myvar;
console.log(exists myvar); // false

Well myvar is declared so how can exists be false?

You use of "declared" is what has thrown me off.

You really mean is "the value of the reference has been set to |undefined| or |null|, either explicitly or by default" right?

(I think this is why so many JS devs avoid |null|. If you don't use |null| in your code, then typeof compared to |undefined| works great).

# Till Schneidereit (12 years ago)

While I like the idea of not having multiple versions of ES very much, I fear that this proposal comes with a danger similar to the problems people have with concatenated source files and the "use strict" pragma. While modules nicely solve the problem of tools doing the wrong thing during some build and optimization process, I'm afraid that an implicit opt-in comes with a huge complexity budget for users.

Current ES users can be told "these are the new features of ES6 that you can only use in modules". For the next generation of users, the story will sound more like "you have to be very careful about where you put what code. Depending on which source file you put a function in, its behavior can vary wildly."

In essence, users would have to keep in mind that there are two contexts in which to write ES, what the differences are between those, and what exactly is allowed in which of them.

Granted: the outcome could very well be that the next generation of ES programmers get taught "put all your code in modules, always" before their first "hello, world". If so: great. But maybe it goes wrong and instead they are taught "there are these newfangled features which only work some of the time and if you move them into the wrong file, they stop working, so just forget about them".

Essentially, I'm afraid that an implicit opt-in could turn out to be an impediment to ES6's adoption instead of easing its path.

till

# Andreas Rossberg (12 years ago)

It sure would be nice to put away with additional language modes. However:

Pragmatically, I don't see how wrapping your program into a module is more convenient then putting a use' directive on top. And your additional proposal for "use module" is kind of admitting that it's actually worse, isn't it? If I ever want to uselet' or `const' in my main program (which I expect to be the common case for most users), then I'm back to square one.

And technically, there is no actual difference between a scoped "mode" and a dependence on syntactic context. The former is just a specific way to provide the latter. Neither is simpler. (In fact, one could argue that piggy-backing modules as a syntactic context for allowing ES6-specific features is both limiting and conflating features.)

And of course, giving up on the top-level and proper static scoping is a big price IMHO. Should we really do that so soon?

In other words, I think the main points of your proposal can essentially be rephrased to:

  1. Rename "use version 6" to "use module".
  2. Allow module declarations in classic mode.
  3. Make every module body start with an implicit "use module".
  4. Keep the semantics of the top-level scope unaltered, even in presence of a top-level "use module".

I'm fine with (1) to (3), but (4) seems to be a separate design choice.

# Gavin Barraclough (12 years ago)

Hey Dave,

I'm definitely in favour of removing the opt-in (or at least commonly removing the need to opt-in), and support the goal of one JS. Since most of the new syntax only relies on reserved words and, as Brendon stated, yield is only valid in function* constructs, the only problem so far as new syntax is concerned would seem to be let? Given how fundamental var is to writing a program in JS, if we intend let to be the new var then it would be a huge loss if this was the one new piece of syntax that wasn't available to all code.

Other than an ASI issue, which we can fix with a no-LineTerminator-here (as for module), I think we can just treat let as a contextual keyword in classic code, and it should be completely backwards compatible? I don't believe any statement beginning with: let Identifier or let { could have been valid ES5 syntax anyway. The situation for: let [ is a little more tricky, since this could be a property access expression, however looking at the rules for ArrayBindingPattern it seems that this must always either be empty, end with a comma, or end with a BindingRestElement? If so, I don't think there is any pattern that will parse as both an ArrayBindingPattern and an Expression.

(Am I reading ArrayBindingPattern correctly, and is this the intention? – the rules for ArrayBindingPattern is a little confusing, since it allows: [ BindingElementList , Elision<opt> BindingRestElement<opt> ]

which appears to be a slightly verbose way of saying: [ BindingElementList Elision BindingRestElement<opt> ]

and there is also similar language in BindingElementList).

One issue with allowing let statements in classic code is that classic code also allows with statements, both constructs make use of the LexicalEnviroment, and as I read it the current runtime semantics could give rise to some, um, interesting behaviour! - but I wouldn't see this as a major concern.

If all new syntax is backwards compatible and enabled in all code then it raises the question, how exactly would the language change inside of a module?

  1. Would we implicitly enable ES5-strict mode inside modules?
  2. Do we change the behaviour of typeof inside modules?
  3. Do we change static scoping / access to the global object inside modules? (Does this cover everything?)

It seems likely that there will be users of the language who will take existing ES5 code, wrap it in module syntax, and expect this to work. If users do so, it seems to me that:

  • It would seem most desirable for existing ES5 code were to just work.
  • Next best would be if certain constructs were to generate an early error, making it impossible for a user to miss the compatibility issue.
  • Less desirable would be for code moved into module to run without generating early errors, but to potentially be able to generate new runtime errors.
  • Least desirable would be for code moved into a module to continue to function without throwing, but with subtly different runtime semantics.

But then what about static scoping?

Compile-time checking of variables is a really important goal, and we don't have to abandon it. We just do the checking only within module code, relative to the current contents of the global object. This means it's still technically possible for globals to disappear at runtime (and consequently throw errors).

If I'm interpreting your proposal correctly then this would only result in new early errors, which I think makes this a very transparent and understandable change for developers – this seems like pure win.

The large majority of changes introduced by strict mode are new syntax errors, so implicitly enabling strict mode seems reasonable, but I have some concern about this – there are new runtime errors (albeit some would probably be covered by your stricter static scoping semantics), and there is the change in runtime behaviour for the handling of the this value.

Brendan's objection withstanding, I'm inclined to agree with you that we should not change the semantics of typeof.

So my answers to my three questions above would be a hesitant yes, no, and yes (per your static scoping proposal).

# Axel Rauschmayer (12 years ago)

({ define: typeof define === "function" ? define // browser : function(F) { F(require,exports,module) } }). // Node.js define(function (require, exports, module) { // Node.js module code goes here });

Sure, but in that case the test typeof define === 'function' tests the value that the variable 'define' references. It does not test if "define' has been declared.

I’m not completely sure I understand what you mean.

typeof define === "function"

  • Node.js: variable "define" has not been declared. Because of that, typeof define is "undefined" and the expression is false.
  • Browser + RequireJS: There is a global variable "define" whose value is a function. Hence typeof define is "function" and the expression is true.

The exists operator would work as follows (slightly edited from a previous email):

console.log(exists globalUnknown); // false

// undefined and null in several variations

var myvar;
console.log(exists myvar); // false

Well myvar is declared so how can exists be false?

Right. Maybe the operator should have a different name. "isDefined"? "has a value" expressed as an operator name?

 isDefined x

would be syntactic sugar for

 typeof x !== "undefined" && x !== null

The expression would not throw an exception if x hasn’t been declared.

The two most important use cases for typeof probably are:

  • isDefined
  • Helping to implement getTypeName() whose results are
    • "null", "undefined", "boolean", "number", "string" for primitive values
    • [[Class]] for objects ("String", "Date", etc.)
# Brendan Eich (12 years ago)

[Dave has been traveling, hope it's ok for me to jump in. /be]

On Jan 2, 2012, at 6:07 AM, Andreas Rossberg wrote:

In other words, I think the main points of your proposal can essentially be rephrased to:

  1. Rename "use version 6" to "use module".
  2. Allow module declarations in classic mode.

[Replying to (1) and (2) here:]

Not just module declarations -- all new syntax that is not backwards-incompatible: destructuring, rest/spread, for/of ,generators, comprehensions, generator expresions, quasiliterals, more.

  1. Make every module body start with an implicit "use module".

That's not right: use module; in a pragma turns the enclosing block or body into a module {...}, in a macro-like way. Then if the module {...} is illegal in the given context (i.e., not nested immediately in another module's body), you get the same error you'd get trying to write, e.g.,

if (cond) { module { /* stuff */ } }

  1. Keep the semantics of the top-level scope unaltered, even in presence of a top-level "use module".

No, see above: that turns the verbatim top level into the ... part of module {...}.

I'm fine with (1) to (3), but (4) seems to be a separate design choice.

With what I wrote above in mind, what do you think now?

# Brendan Eich (12 years ago)

On Jan 1, 2012, at 5:12 AM, David Bruant wrote:

Moreover, the concept of static scoping and "just look at the code to see if a variable is declared" is not that obvious for newcomers to the language or newcomers to programming.

It's not obvious if the static scope is built up from <sript> element to successive <script> element, either. Must I read all the scripts? The conditionally generated ones too?

The top level is hard. The only way to be sure is to use pure lexical scope (in Dave's proposal, use module {...}).

# Axel Rauschmayer (12 years ago)

Right. Maybe the operator should have a different name. "isDefined"? "has a value" expressed as an operator name?

 isDefined x

would be syntactic sugar for

 typeof x !== "undefined" && x !== null

The expression would not throw an exception if x hasn’t been declared.

What about adding CoffeeScript's existential operator? It behaves in the exact same way - x? desugars into typeof x !== "undefined" && x !== null. Could be nice to have that as part of ES.next.

One more important reason to add this (whether as x? or as a regular operator) is that it can't be implemented in user-land code (calling it with a non-existing variable would cause a ReferenceError).

+1

?? has been suggested by Crockford (if you include Eich’s suggestion to check for null, as well).

 http://wiki.ecmascript.org/doku.php?id=strawman:default_operator

?? would work better than ? in JavaScript, because it wouldn’t clash with the conditional operator in JavaScript (which CoffeeScript doesn’t need, due to its functional if statement). Then the thing to add to the proposal would be ?? used as a unary postfix operator:

if (x??) ...

Prefix might work, too.

# Gavin Barraclough (12 years ago)

On Jan 2, 2012, at 11:45 PM, Axel Rauschmayer wrote:

Prefix might work, too.

¿x?

# Andreas Rossberg (12 years ago)

On 3 January 2012 07:21, Brendan Eich <brendan at mozilla.com> wrote:

It's not obvious if the static scope is built up from <sript> element to successive <script> element, either. Must I read all the scripts? The conditionally generated ones too?

The top level is hard. The only way to be sure is to use pure lexical scope (in Dave's proposal, use module {...}).

Ah, but wrapping into modules is incompatible with having multiple script parts. For multi-part scripts we need a way to switch the proper top-level into extended mode. Or should I not be able to write (the relevant bits of) a multi-part script in extended mode at all?

# Andreas Rossberg (12 years ago)

On 3 January 2012 07:19, Brendan Eich <brendan at mozilla.com> wrote:

[Dave has been traveling, hope it's ok for me to jump in. /be]

On Jan 2, 2012, at 6:07 AM, Andreas Rossberg wrote:

In other words, I think the main points of your proposal can essentially be rephrased to:

  1. Rename "use version 6" to "use module".
  2. Allow module declarations in classic mode.

[Replying to (1) and (2) here:]

Not just module declarations -- all new syntax that is not backwards-incompatible: destructuring, rest/spread, for/of ,generators, comprehensions, generator expresions, quasiliterals, more.

Yes, but that is a totally independent choice. We could do that regardless of Dave's proposal. And I agree, there probably is no good reason not to. Maybe we can even get "let" in?

  1. Make every module body start with an implicit "use module".

That's not right: use module; in a pragma turns the enclosing block or body into a module {...}, in a macro-like way. Then if the module {...} is illegal in the given context (i.e., not nested immediately in another module's body), you get the same error you'd get trying to write, e.g.,

if (cond) { module { /* stuff */ } }

Hm, I don't follow. How is this related to point (3)? The body of a module always is a context where module declarations are allowed.

Isn't it rather a reply to my item (1) above? If so, my assumption always has been that "use version 6" would only be allowed on the top-level, and not fine-grained in individual functions like "use strict", since that would run havoc with lexical scoping.

  1. Keep the semantics of the top-level scope unaltered, even in presence of a top-level "use module".

No, see above: that turns the verbatim top level into the ... part of module {...}.

OK, I see, but then we effectively have opted out of the global object, at least as a carrier for the verbatim top-level declarations of the (current) script, haven't we? So I'm not sure how that even differs substantially from what we have been discussing so far regarding the global object. We could still decide to (or not to) remove the global object from the scope chain entirely in that case.

I'm fine with (1) to (3), but (4) seems to be a separate design choice.

With what I wrote above in mind, what do you think now?

Since I'm no longer clear what change (relative to our, admittedly vague, recent discussion) Dave is actually proposing wrt the top-level, I don't know. Maybe there is no (4) at all, which I'd be fine with, of course. :)

# Brendan Eich (12 years ago)

On Jan 3, 2012, at 1:29 AM, Andreas Rossberg wrote:

On 3 January 2012 07:21, Brendan Eich <brendan at mozilla.com> wrote:

It's not obvious if the static scope is built up from <sript> element to successive <script> element, either. Must I read all the scripts? The conditionally generated ones too?

The top level is hard. The only way to be sure is to use pure lexical scope (in Dave's proposal, use module {...}).

Ah, but wrapping into modules is incompatible with having multiple script parts.

I don't know what you mean. "Incompatible" in the sense that you cannot transform multiple scripts into multiple anonymous modules?

For multi-part scripts we need a way to switch the proper top-level into extended mode. Or should I not be able to write (the relevant bits of) a multi-part script in extended mode at all?

The proposal may have been unclear on this point: the top level would allow as much new syntax and semantics as can be tolerated backwards-compatibly. The hard cases are let, lexical scope in the free-variables-are-errors sense Dave described (extant globals at start of module body are imported), and any runtime shifts we want (completion reform, typeof null).

# Allen Wirfs-Brock (12 years ago)

On Jan 3, 2012, at 12:01 PM, Brendan Eich wrote:

On Jan 3, 2012, at 1:29 AM, Andreas Rossberg wrote:

...

For multi-part scripts we need a way to switch the proper top-level into extended mode. Or should I not be able to write (the relevant bits of) a multi-part script in extended mode at all?

The proposal may have been unclear on this point: the top level would allow as much new syntax and semantics as can be tolerated backwards-compatibly. The hard cases are let, lexical scope in the free-variables-are-errors sense Dave described (extant globals at start of module body are imported), and any runtime shifts we want (completion reform, typeof null).

I suspect there will also be issue related to legacy function declarations within blocks.

# Brendan Eich (12 years ago)

On Jan 3, 2012, at 1:29 AM, Andreas Rossberg wrote:

On 3 January 2012 07:19, Brendan Eich <brendan at mozilla.com> wrote:

[Dave has been traveling, hope it's ok for me to jump in. /be]

On Jan 2, 2012, at 6:07 AM, Andreas Rossberg wrote:

In other words, I think the main points of your proposal can essentially be rephrased to:

  1. Rename "use version 6" to "use module".
  2. Allow module declarations in classic mode.

[Replying to (1) and (2) here:]

Not just module declarations -- all new syntax that is not backwards-incompatible: destructuring, rest/spread, for/of ,generators, comprehensions, generator expresions, quasiliterals, more.

Yes, but that is a totally independent choice.

There are several independent choices bundled into this new "ES6 doesn't need opt-in" proposal, but the unifying theme is this: new features that pose no backward incompatibility hardship and are contained or expressed by new syntax need only that new syntax as opt-in.

We could do that regardless of Dave's proposal. And I agree, there probably is no good reason not to. Maybe we can even get "let" in?

Maybe. We tried in 2006-2007 and ran into at least this:

bugzilla.mozilla.org/show_bug.cgi?id=351515

where 'yield' was used as a parameter name. I dimly recall 'let' in the wild but may be misremembering. Perhaps out of paranoia we made both 'let' and 'yield' require version opt-in.

The site that used 'yield' has since been updated to avoid using 'yield'. So we could try again to reserve 'let' unconditionally. Heaven knows I've been yapping about 'let' as the new 'var' long enough to warn most developers away from it!

I discussed this on IRC briefly with Oliver, who seemed game. The only issue I see is that nightly builds (WebKit, Chrome, Firefox) don't get enough use to do other than find true positives. It would be good to find such 'let' usage in the wild, of course, but finding nothing won't give us a green light, just lack of a red light.

  1. Make every module body start with an implicit "use module".

That's not right: use module; in a pragma turns the enclosing block or body into a module {...}, in a macro-like way. Then if the module {...} is illegal in the given context (i.e., not nested immediately in another module's body), you get the same error you'd get trying to write, e.g.,

if (cond) { module { /* stuff */ } }

Hm, I don't follow. How is this related to point (3)? The body of a module always is a context where module declarations are allowed.

Your (3) seemed to say "use module" was something distinct from (and also implicit in, so a part but not the whole of what is declared by) module declaration syntax. It's not -- as proposed, it's an alternative to explicit anonymous module{...} bracketing syntax that translates the block or body enclosing the pragma to an anonymous module.

Isn't it rather a reply to my item (1) above?

No, I wasn't talking about ES6 opt-in, only the meaning of the proposed

use module;

pragma.

  1. Keep the semantics of the top-level scope unaltered, even in presence of a top-level "use module".

No, see above: that turns the verbatim top level into the ... part of module {...}.

OK, I see, but then we effectively have opted out of the global object, at least as a carrier for the verbatim top-level declarations of the (current) script, haven't we?

No. Dave wrote "Short answer: giving up" and "You can still do it with a custom loader".

That is, even in a module (declared at top level either implicitly via the |use module;| pragma or explicitly via module id? {...}), the proposal still keeps the global object on the scope chain. But the proposal does a free variable analysis based on importing the properties of the global at the start of the module's body, and any free variables are early errors.

I'm fine with (1) to (3), but (4) seems to be a separate design choice.

With what I wrote above in mind, what do you think now?

Since I'm no longer clear what change (relative to our, admittedly vague, recent discussion) Dave is actually proposing wrt the top-level, I don't know. Maybe there is no (4) at all, which I'd be fine with, of course. :)

No (4) as you wrote it. Semantics do change to throw early errors for free variables (typo'ed globals, e.g.). Semantics of the global object on the scope chain otherwise do not change with the default module loader.

# Brendan Eich (12 years ago)

On Jan 3, 2012, at 12:16 PM, Allen Wirfs-Brock wrote:

On Jan 3, 2012, at 12:01 PM, Brendan Eich wrote:

On Jan 3, 2012, at 1:29 AM, Andreas Rossberg wrote:

...

For multi-part scripts we need a way to switch the proper top-level into extended mode. Or should I not be able to write (the relevant bits of) a multi-part script in extended mode at all?

The proposal may have been unclear on this point: the top level would allow as much new syntax and semantics as can be tolerated backwards-compatibly. The hard cases are let, lexical scope in the free-variables-are-errors sense Dave described (extant globals at start of module body are imported), and any runtime shifts we want (completion reform, typeof null).

I suspect there will also be issue related to legacy function declarations within blocks.

Possibly, given the intersection semantics between IE and its followers and Firefox are such that you can use a function declared in a block after the block ends, if control flow reached the declaration.

Legacy const is less of an issue since IE did not bite that apple.

# Gavin Barraclough (12 years ago)

On Jan 3, 2012, at 12:18 PM, Brendan Eich wrote:

Maybe. We tried in 2006-2007 and ran into at least this:

bugzilla.mozilla.org/show_bug.cgi?id=351515

where 'yield' was used as a parameter name. I dimly recall 'let' in the wild but may be misremembering. Perhaps out of paranoia we made both 'let' and 'yield' require version opt-in.

The site that used 'yield' has since been updated to avoid using 'yield'. So we could try again to reserve 'let' unconditionally. Heaven knows I've been yapping about 'let' as the new 'var' long enough to warn most developers away from it!

I discussed this on IRC briefly with Oliver, who seemed game. The only issue I see is that nightly builds (WebKit, Chrome, Firefox) don't get enough use to do other than find true positives. It would be good to find such 'let' usage in the wild, of course, but finding nothing won't give us a green light, just lack of a red light.

Why unconditionally reserve let? - would it not make more sense to handle this in a contextual fashion if we can do so? – if so, we could introduce 'let' without any backwards compatibility risk, and retain the option to promote it to a keyword at a later date.

G.

# Mark S. Miller (12 years ago)

Just Two Modes

This is a long thread and I've been completely busy with other things so have not had time to do more than skim. So please understand if the post below misses some context. The following is a summary of some principles that Dave and just agreed to in a verbal conversation, but he hasn't had the chance to look at the following text before I send it, so it may not quite speak for our agreement -- I've substantially elaborated it since the text that Dave and I looked at together. Dave introduced this thread with the slogan "just one JavaScript", so I'll introduce the following with the (much less catchy) slogan "just two modes".

  • ES5's strict vs non-strict distinction remains the only mode distinction. ES6 thus has only the same two modes.

  • ES6 non-strict mode must be practically upwards compatible from ES5 non-strict mode.

  • ES6 strict mode must be practically upwards compatible from ES5 strict mode.

  • In ES6, one can opt-in to strict mode in at least the following two ways:

    "use strict"; // exactly as in ES5

or

module <ident>? {

in statement context. In other words, exactly as ES5 may begin strict mode at a function boundary to apply to everything recursively contained lexically within the function, in ES6 in addition, strict mode may also begin at a module boundary and apply to everything recursively within the module. Code recursively contained within a module is always strict; there's no way to write non-strict code within a module. But a module, like a function, may be embedded within a non-strict context.

  • Code that contains such a module construct may run on an ES5 system or may cause an early SyntaxError, depending on whether this ES5 implementation has been extended to recognize the module construct. An ES6 system must of course recognize the module construct. Thus, modules, as well as most other features of ES6, may be deployed incrementally, just as many features of ES5 were deployed incrementally in the transition from ES3 to ES5.

  • We give up typeof reform.

  • We do completion reform only if we judge it to be practically upward compatible, with a dispensation to ES5 implementations to implement it without penalty of being non-conformant. (Dave and I both expect it will in fact be practically upwards compatible.)

  • As with completion reform, if there are other cleanups we can make to ES5 that is practically upwards compatible, e.g., whose only incompatibility is with test262, we can consider these for ES6 and absolve ES5 systems that adopt these cleanups.

  • We obtain a clean top level scope only by using loaders, which is increasingly how I've been thinking of SES anyway.

  • The identifiers that are reserved in ES5 only in strict mode are:

    implements, interface, let, package, private, protected, public, static, yield ES6 features that use these keywords are available only in strict mode. Because ES5 reserved them, this is fully upwards compatible with ES5. For other ES6 features that do not depend on these keywords, whether or not they must also be available in ES6 non-strict code we need to take on a case by case basis.

  • In ES6, nested named function declaration must be accepted and have the agreed ES6 semantics in strict code. As advised at < conventions:no_non_standard_strict_decls>,

all major browsers currently reject nested named function declaration in strict code, so accepting them with the agreed semantics in ES6-strict is fully upwards compatible.ES6 remains as silent as ES5 about whether nested named function declarations can appear in non-strict code or what their semantics is there.

# Brendan Eich (12 years ago)

On Jan 3, 2012, at 12:28 PM, Gavin Barraclough wrote:

On Jan 3, 2012, at 12:18 PM, Brendan Eich wrote:

Maybe. We tried in 2006-2007 and ran into at least this:

bugzilla.mozilla.org/show_bug.cgi?id=351515

where 'yield' was used as a parameter name. I dimly recall 'let' in the wild but may be misremembering. Perhaps out of paranoia we made both 'let' and 'yield' require version opt-in.

The site that used 'yield' has since been updated to avoid using 'yield'. So we could try again to reserve 'let' unconditionally. Heaven knows I've been yapping about 'let' as the new 'var' long enough to warn most developers away from it!

I discussed this on IRC briefly with Oliver, who seemed game. The only issue I see is that nightly builds (WebKit, Chrome, Firefox) don't get enough use to do other than find true positives. It would be good to find such 'let' usage in the wild, of course, but finding nothing won't give us a green light, just lack of a red light.

Why unconditionally reserve let? - would it not make more sense to handle this in a contextual fashion if we can do so? – if so, we could introduce 'let' without any backwards compatibility risk, and retain the option to promote it to a keyword at a later date.

You're proposing that we require 'let' be only at the start of statements? If so, then destructuring is problematic:

foo(); let [x] = y;

Did that last line destructure the property named '0' of the object denoted by y into a let-bound x, or was it old, pre-ES6 code that stored y into the x'th element of an object denoted 'let'? In either case the 'let' is at the start of a statement.

# Brendan Eich (12 years ago)

On Jan 3, 2012, at 1:24 PM, Mark S. Miller wrote:

                       Just Two Modes

V8 folks the other year had a catchier version: "no more modes".

  • ES6 non-strict mode must be practically upwards compatible from ES5 non-strict mode.

This is the part that's not clearly agreed to, or perhaps not even clearly proposed yet.

Dave alluded to how we can decide on a case-by-case basis how new syntax using already-reserved (since ES1) keywords could be meaningful in <script> (no version) if the engine supports ES6 (or ES7, later, etc.).

For example, class. If we support class in ES6, why shouldn't it be usable at top level not in a module (via module{...} or |use module;|)? The syntax is a guaranteed error in downrev engines.

Same for destructuring, rest/spread, etc. as I've mentioned in recent messages.

Why you write "upwards compatible" you may mean something other than what's defined at

en.wikipedia.org/wiki/Forward_compatibility

viz, new code degrades gracefully on old engines. If you mean that old code runs in new engines without changed semantics, that is downward or backward compatibility. I'm not sure what you meant so I'll stop here.

# Gavin Barraclough (12 years ago)

On Jan 3, 2012, at 3:27 PM, Brendan Eich wrote:

You're proposing that we require 'let' be only at the start of statements? If so, then destructuring is problematic:

foo(); let [x] = y;

Did that last line destructure the property named '0' of the object denoted by y into a let-bound x, or was it old, pre-ES6 code that stored y into the x'th element of an object denoted 'let'? In either case the 'let' is at the start of a statement.

/be

Based on the draft of the spec I have, I don't think that is an actual ambiguity here (albeit naming an array 'let' could be very confusing!). This could of course either be an error in my reading of the spec, or a bug in the current draft of the grammar. :-)

The rules for destructuring give:

ArrayBindingPattern : [ Elision<opt> BindingRestElement<opt> ] [ BindingElementList , Elision<opt> BindingRestElement<opt> ]

Note the literal comma required after any BindingElementList - as I parse the grammar your example is not valid ES6, to destructure the first element from an array you would have to write:

foo(); let [x,] = y;

As such, the example you give could only be storing y to a property x of object let (or a syntax error in ES5-strict / ES6 modes).

Is my reading of the spec incorrect here? Is this the intention?

# Allen Wirfs-Brock (12 years ago)

On Jan 3, 2012, at 4:31 PM, Gavin Barraclough wrote:

On Jan 3, 2012, at 3:27 PM, Brendan Eich wrote:

You're proposing that we require 'let' be only at the start of statements? If so, then destructuring is problematic:

foo(); let [x] = y;

Did that last line destructure the property named '0' of the object denoted by y into a let-bound x, or was it old, pre-ES6 code that stored y into the x'th element of an object denoted 'let'? In either case the 'let' is at the start of a statement.

/be

Based on the draft of the spec I have, I don't think that is an actual ambiguity here (albeit naming an array 'let' could be very confusing!). This could of course either be an error in my reading of the spec, or a bug in the current draft of the grammar. :-)

The rules for destructuring give:

ArrayBindingPattern : [ Elision<opt> BindingRestElement<opt> ] [ BindingElementList , Elision<opt> BindingRestElement<opt> ]

It's a grammar bug. Thanks for the proof reading...

# Brendan Eich (12 years ago)

On Jan 3, 2012, at 4:31 PM, Gavin Barraclough wrote:

On Jan 3, 2012, at 3:27 PM, Brendan Eich wrote:

You're proposing that we require 'let' be only at the start of statements? If so, then destructuring is problematic:

foo(); let [x] = y;

Did that last line destructure the property named '0' of the object denoted by y into a let-bound x, or was it old, pre-ES6 code that stored y into the x'th element of an object denoted 'let'? In either case the 'let' is at the start of a statement.

/be

Based on the draft of the spec I have, I don't think that is an actual ambiguity here (albeit naming an array 'let' could be very confusing!). This could of course either be an error in my reading of the spec, or a bug in the current draft of the grammar. :-)

The rules for destructuring give:

ArrayBindingPattern : [ Elision<opt> BindingRestElement<opt> ] [ BindingElementList , Elision<opt> BindingRestElement<opt> ]

Note the literal comma required after any BindingElementList - as I parse the grammar your example is not valid ES6, to destructure the first element from an array you would have to write:

foo(); let [x,] = y;

No, the grammar is based on ArrayLiteral and produces the same "shapes", just with identifiers in the value positions. See the first right-hand side, interpreted with no Elision but with a BindingRestElement (which produces BindingIdentifier).

# Brendan Eich (12 years ago)

On Jan 3, 2012, at 4:38 PM, Allen Wirfs-Brock wrote:

ArrayBindingPattern : [ Elision<opt> BindingRestElement<opt> ] [ BindingElementList , Elision<opt> BindingRestElement<opt> ]

It's a grammar bug. Thanks for the proof reading...

The final <opt> cited above is the bug, right?

The first right-hand side still is enough to produce the [x] pattern.

# Gavin Barraclough (12 years ago)

On Jan 3, 2012, at 4:42 PM, Brendan Eich wrote:

On Jan 3, 2012, at 4:38 PM, Allen Wirfs-Brock wrote:

ArrayBindingPattern : [ Elision<opt> BindingRestElement<opt> ] [ BindingElementList , Elision<opt> BindingRestElement<opt> ]

It's a grammar bug. Thanks for the proof reading...

The final <opt> cited above is the bug, right?

The first right-hand side still is enough to produce the [x] pattern.

/be

I assumed the bug was the presence of a literal comma?

G.

# Allen Wirfs-Brock (12 years ago)

On Jan 3, 2012, at 4:42 PM, Brendan Eich wrote:

On Jan 3, 2012, at 4:38 PM, Allen Wirfs-Brock wrote:

ArrayBindingPattern : [ Elision<opt> BindingRestElement<opt> ] [ BindingElementList , Elision<opt> BindingRestElement<opt> ]

It's a grammar bug. Thanks for the proof reading...

The final <opt> cited above is the bug, right?

The first right-hand side still is enough to produce the [x] pattern.

The current spec. draft is missing the second (of three) LHS rules that show up in ES5.1 for ArrayLiteral. It believe the correct form should be:

ArrayBindingPattern : [ Elisionopt BindingRestElementopt ] [ BindingElementList ] [ BindingElementList , Elisionopt BindingRestElementopt ]

# Brendan Eich (12 years ago)

On Jan 3, 2012, at 4:50 PM, Gavin Barraclough wrote:

On Jan 3, 2012, at 4:42 PM, Brendan Eich wrote:

On Jan 3, 2012, at 4:38 PM, Allen Wirfs-Brock wrote:

ArrayBindingPattern : [ Elision<opt> BindingRestElement<opt> ] [ BindingElementList , Elision<opt> BindingRestElement<opt> ]

It's a grammar bug. Thanks for the proof reading...

The final <opt> cited above is the bug, right?

The first right-hand side still is enough to produce the [x] pattern.

/be

I assumed the bug was the presence of a literal comma?

No, compare to ArrayLiteral:

ArrayLiteral : // See 11.1.4 [ Elisionopt ] [ ElementList ] [ ElementList , Elisionopt ]

ElementList : // See 11.1.4 Elisionopt AssignmentExpression ElementList , Elisionopt AssignmentExpression

Replace ArrayLiteral with ArrayBindingPattern, ElementList with BindingElementList, and AssignmentExpression with BindingRestElement.

# Gavin Barraclough (12 years ago)

Ah, I see! - Thanks.

# Allen Wirfs-Brock (12 years ago)

On Jan 3, 2012, at 4:58 PM, Brendan Eich wrote:

...

No, compare to ArrayLiteral:

ArrayLiteral : // See 11.1.4 [ Elisionopt ] [ ElementList ] [ ElementList , Elisionopt ]

ElementList : // See 11.1.4 Elisionopt AssignmentExpression ElementList , Elisionopt AssignmentExpression

Replace ArrayLiteral with ArrayBindingPattern, ElementList with BindingElementList, and AssignmentExpression with BindingRestElement.

But note that ES.next ArrayLiterals are different from ArrayBindingPatterns. the ... in ArrayLiteral is the spread operator and can occur multiple times and in any position. The ... in ArrayBindingPattern is the rest operator and can only occur once at the end of the list.

# Mark S. Miller (12 years ago)

On Tue, Jan 3, 2012 at 4:08 PM, Brendan Eich <brendan at mozilla.com> wrote:

On Jan 3, 2012, at 1:24 PM, Mark S. Miller wrote:

                       Just Two Modes

V8 folks the other year had a catchier version: "no more modes".

I am happy with that ;).

  • ES6 non-strict mode must be practically upwards compatible from ES5 non-strict mode.

This is the part that's not clearly agreed to, or perhaps not even clearly proposed yet.

Dave alluded to how we can decide on a case-by-case basis how new syntax using already-reserved (since ES1) keywords could be meaningful in <script> (no version) if the engine supports ES6 (or ES7, later, etc.).

For example, class.

A good example. Since the class proposal uses "private, public, static", by these principles, it would only be recognized in strict mode. If it appears in non-strict code, it would be a SyntaxError.

If we support class in ES6, why shouldn't it be usable at top level not in a module (via module{...} or |use module;|)? The syntax is a guaranteed error in downrev engines.

It would be, so long as that code is strict. Just say "use strict"; outside a module and you can use classes.

Same for destructuring, rest/spread, etc. as I've mentioned in recent messages.

"let" would only be allowed in strict code. ES5 non-strict allows "let" to be used as a variable name, so ES6 non-strict should too.

Want to use "let" scoping outside a module? Again, just say "use strict";.

What remaining issues are there in using destructuring, spread, or rest in non-strict code? The short answer, not knowing what conflicts you have identified, is that if it is painless and backward-compatible (see below) to accept these in both strict and non-strict, then we should. Otherwise, we accept these only in strict code.

Why you write "upwards compatible" you may mean something other than what's defined at

en.wikipedia.org/wiki/Forward_compatibility

viz, new code degrades gracefully on old engines. If you mean that old code runs in new engines without changed semantics, that is downward or backward compatibility. I'm not sure what you meant so I'll stop here.

I mean that "old code runs in new engines without [practically] changed semantics". Sorry for the confusion.

I insert [practically] above to cover cases like completion reform. Although it is not formally backward compat, if it doesn't break any real programs besides test262 tests, we should go for it and arrange to forgive ES5 engines that implement it.

# Brendan Eich (12 years ago)

On Jan 3, 2012, at 9:52 PM, Mark S. Miller wrote:

A good example. Since the class proposal uses "private, public, static", by these principles, it would only be recognized in strict mode. If it appears in non-strict code, it would be a SyntaxError.

That's a choice. I'm asking why it's the best one. Just because class syntax might involve new reserved identifiers doesn't require strict mode. The introductory keyword is class and the new keywords are all in the braced body.

The idea is that the always-reserved 'class' keyword is opt-in enough -- no need for a "use strict"; string litearl (ignored by old browsers anyway!).

It would be, so long as that code is strict. Just say "use strict"; outside a module and you can use classes.

Why is this the best design, though? Why not just say 'class' followed by a head and body per whatever spec we agree on? (Assume we agree on one for some future edition. ;-)

Want to use "let" scoping outside a module? Again, just say "use strict";.

The string literal directive is meaningless in old browsers, and the other new syntax than let is guaranteed to fail with a syntax error in old engines, so what's the benefit of requiring the extra "use strinct"; again?

What remaining issues are there in using destructuring, spread, or rest in non-strict code? The short answer, not knowing what conflicts you have identified, is that if it is painless and backward-compatible (see below) to accept these in both strict and non-strict, then we should. Otherwise, we accept these only in strict code.

Ok, we should get into the particulars. It wasn't clear until this paragraph that any new features would be available in non-strict mode, in your view of the proposal. But in talking to Dave about it pre-launch I'm pretty sure we contemplated such a case-by-case "use new syntax to opt into ES6 from any mode" approach.

I insert [practically] above to cover cases like completion reform. Although it is not formally backward compat, if it doesn't break any real programs besides test262 tests, we should go for it and arrange to forgive ES5 engines that implement it.

I agree on completion reform, but Oliver is game to try reserving 'let', and (typeof null == "null") still beckons. We can draw a more conservative line but IMHO it should not require "use strict"; to opt in if new syntax is unambiguous and starts with a reserved identifier or previously-illegal token sequence.

# Mark S. Miller (12 years ago)

On Tue, Jan 3, 2012 at 10:18 PM, Brendan Eich <brendan at mozilla.com> wrote:

On Jan 3, 2012, at 9:52 PM, Mark S. Miller wrote:

A good example. Since the class proposal uses "private, public, static", by these principles, it would only be recognized in strict mode. If it appears in non-strict code, it would be a SyntaxError.

That's a choice. I'm asking why it's the best one. Just because class syntax might involve new reserved identifiers doesn't require strict mode. The introductory keyword is class and the new keywords are all in the braced body.

The idea is that the always-reserved 'class' keyword is opt-in enough -- no need for a "use strict"; string litearl (ignored by old browsers anyway!).

"opt-in enough", interesting. I'm not sure if this is what you meant, but it suggests a very attractive option I hadn't considered: treat "class", like "module", as yet another way to opt-in to strict mode. All code recursively inside a class would be strict.

It would be, so long as that code is strict. Just say "use strict"; outside a module and you can use classes.

Why is this the best design, though? Why not just say 'class' followed by a head and body per whatever spec we agree on? (Assume we agree on one for some future edition. ;-)

For class, you're right. I really like the idea of having it be another way to opt-in to strict mode. What other constructs might join "class" and "module" as new strict-mode opt-ins?

Want to use "let" scoping outside a module? Again, just say "use strict";.

The string literal directive is meaningless in old browsers, and the other new syntax than let is guaranteed to fail with a syntax error in old engines, so what's the benefit of requiring the extra "use strinct"; again?

Huh? In ES5, the following code is legal:

var let = 88;

The following code is not:

function foo() { "use strict"; var let = 88; }

I don't want to twist the grammar to turn "let" into a context sensitive reserved word so that somehow

"var let = 88;" and use of let-scoping can co-exist in the same mode.

This seems like too high a price to pay simply to avoid any of the strict mode opt ins. Any code under active maintenance should opt-in to strict somehow anyway. Non-strict mode is only for legacy that needs to keep working without any further human attention -- most of the web. This code already doesn't use let scoping, so what's the issue?

What remaining issues are there in using destructuring, spread, or rest in non-strict code? The short answer, not knowing what conflicts you have identified, is that if it is painless and backward-compatible (see below) to accept these in both strict and non-strict, then we should. Otherwise, we accept these only in strict code.

Ok, we should get into the particulars. It wasn't clear until this paragraph that any new features would be available in non-strict mode, in your view of the proposal. But in talking to Dave about it pre-launch I'm pretty sure we contemplated such a case-by-case "use new syntax to opt into ES6 from any mode" approach.

Yes. In my initial email I said "For other ES6 features that do not depend on these keywords, whether or not they must also be available in ES6 non-strict code we need to take on a case by case basis."

I insert [practically] above to cover cases like completion reform. Although it is not formally backward compat, if it doesn't break any real programs besides test262 tests, we should go for it and arrange to forgive ES5 engines that implement it.

I agree on completion reform, but Oliver is game to try reserving 'let', and (typeof null == "null") still beckons. We can draw a more conservative line but IMHO it should not require "use strict"; to opt in if new syntax is unambiguous and starts with a reserved identifier or previously-illegal token sequence.

Again, the criteria I care about is "practically backwards compatible". If it works to simply make "let" an unconditional keyword, then we should. On this one, I'm skeptical. It is a short three letter english word. If there are not many uses of it as a variable name, I would be very surprised. On typeof, I am more than skeptical. I think this one is likely impossible without a third mode. But if the data says otherwise, we should go with the data.

But the "previously-illegal token sequence" approach complicates the grammar and readability in ways that are not worth it, just to avoid any of the means of opting in to strict, which all non-legacy code should do anyway. For modules, we'd be taking the "previously-illegal token sequence" approach in order to avoid introducing a third mode, which is so valuable that the grammar and readability complexities are worth it.

# Andreas Rossberg (12 years ago)

On 3 January 2012 21:18, Brendan Eich <brendan at mozilla.com> wrote:

On Jan 3, 2012, at 1:29 AM, Andreas Rossberg wrote:

On 3 January 2012 07:19, Brendan Eich <brendan at mozilla.com> wrote:

On Jan 2, 2012, at 6:07 AM, Andreas Rossberg wrote:

In other words, I think the main points of your proposal can essentially be rephrased to:

  1. Rename "use version 6" to "use module".
  2. Allow module declarations in classic mode. [...]
  1. Make every module body start with an implicit "use module".

That's not right: use module; in a pragma turns the enclosing block or body into a module {...}, in a macro-like way. Then if the module {...} is illegal in the given context (i.e., not nested immediately in another module's body), you get the same error you'd get trying to write, e.g.,

if (cond) { module { /* stuff */ } }

Hm, I don't follow. How is this related to point (3)? The body of a module always is a context where module declarations are allowed.

Your (3) seemed to say "use module" was something distinct from (and also implicit in, so a part but not the whole of what is declared by) module declaration syntax. It's not -- as proposed, it's an alternative to explicit anonymous module{...} bracketing syntax that translates the block or body enclosing the pragma to an anonymous module.

Yes, I understand that. My point was that you can reformulate all that, for (almost) equivalent effect, by saying that "use module" is not a module definition, but basically the same mode pragma as before, except that it now is implicit with every module body.

The only difference I can see with this description is its effect on the semantics of multi-part scripts.

  1. Keep the semantics of the top-level scope unaltered, even in presence of a top-level "use module".

No, see above: that turns the verbatim top level into the ... part of module {...}.

OK, I see, but then we effectively have opted out of the global object, at least as a carrier for the verbatim top-level declarations of the (current) script, haven't we?

No. Dave wrote "Short answer: giving up" and "You can still do it with a custom loader".

That is, even in a module (declared at top level either implicitly via the |use module;| pragma or explicitly via module id? {...}), the proposal still keeps the global object on the scope chain. But the proposal  does a free variable analysis based on importing the properties of the global at the start of the module's body, and any free variables are early errors.

Those quotes from Dave's post are exactly the bits I couldn't make sense of properly. Because even before his proposal, I remember what you describe being an option we were still considering. Thanks for clarifying.

# Andreas Rossberg (12 years ago)

On 3 January 2012 21:01, Brendan Eich <brendan at mozilla.com> wrote:

On Jan 3, 2012, at 1:29 AM, Andreas Rossberg wrote:

On 3 January 2012 07:21, Brendan Eich <brendan at mozilla.com> wrote:

The top level is hard. The only way to be sure is to use pure lexical scope (in Dave's proposal, use module {...}).

Ah, but wrapping into modules is incompatible with having multiple script parts.

I don't know what you mean. "Incompatible" in the sense that you cannot transform multiple scripts into multiple anonymous modules?

Yes.

For multi-part scripts we need a way to switch the proper top-level into extended mode. Or should I not be able to write (the relevant bits of) a multi-part script in extended mode at all?

The proposal may have been unclear on this point: the top level would allow as much new syntax and semantics as can be tolerated backwards-compatibly. The hard cases are let, lexical scope in the free-variables-are-errors sense Dave described (extant globals at start of module body are imported), and any runtime shifts we want (completion reform, typeof null).

I suspect there may be other subtle issues, e.g., what about `const' and local functions? [I see, Allen mentioned that already.]

In any case, even if we allow more features in classic mode, that still doesn't give you the ability to use all Harmony features for multi-part scripts. I think we need a proper story for this.

[I just saw that later down the thread Mark proposed reusing strict mode as an opt-in for full Harmony. I reply to that separately.]

# Andreas Rossberg (12 years ago)

Mark's reformulation of the proposal is somewhat different from how I initially interpreted it -- in particular, wrt tying (merging) extended mode to strict mode. But it seems like the logical conclusion.

The main problem with the proposal (in both Dave's original form and with Mark's refinement) is that it essentially gives up the idea that Harmony builds on strict mode. It is not at all obvious to me what implications that will have. A few observations:

  • Despite the superficial "fewer modes" mantra, it actually doesn't make the language simpler, but more complicated: instead of defining the semantics for Harmony features only for strict-mode programs, we now have to define many features for both modes (and mixed uses of both modes). I.e., there are more syntactic and semantic combinations to worry about.

  • Providing Harmony features in classic mode decreases the incentive for users to upgrade to strict mode. Is that a good thing?

  • After Dave's posting I foresaw people wanting implicit opt-in with other constructs, e.g. classes or uses of `import' -- and voila, we're already there. But even with that, you still have to rely on explicit opt-in (via "use strict"?) for enabling some features elsewhere. As a result, we expect programmers to remember two separate, fairly random (for anybody not intimate with the history) lists of features that (1) require opt-in, and (2) imply opt-in. Style guides will probably suggest to put "use strict" on top of every Harmony program to escape the mess.

  • All this together (new features in old mode, implicit opt-in, explicit opt-in) makes it more subtle to see what piece of ES6 code is strict and which isn't. This is a disadvantage when reading code and a potential pitfall for refactoring (probably much worse than strict mode today).

  • Finally, not having a new mode restricts our ability to clean up the language beyond what's already in strict mode (see typeof).

Considering all that, I can't help feeling that having a separate mode is cleaner, simpler, and easier to use. I think it also has more potential for providing a robust foundation for future evolution of the language.

# Mark S. Miller (12 years ago)

On Wed, Jan 4, 2012 at 4:23 AM, Andreas Rossberg <rossberg at google.com>wrote:

Mark's reformulation of the proposal is somewhat different from how I initially interpreted it -- in particular, wrt tying (merging) extended mode to strict mode. But it seems like the logical conclusion.

The main problem with the proposal (in both Dave's original form and with Mark's refinement) is that it essentially gives up the idea that Harmony builds on strict mode. It is not at all obvious to me what implications that will have. A few observations:

  • Despite the superficial "fewer modes" mantra, it actually doesn't make the language simpler, but more complicated: instead of defining the semantics for Harmony features only for strict-mode programs, we now have to define many features for both modes (and mixed uses of both modes). I.e., there are more syntactic and semantic combinations to worry about.

  • Providing Harmony features in classic mode decreases the incentive for users to upgrade to strict mode. Is that a good thing?

Here's an interesting compromise I consider perfectly reasonable. We don't mandate any ES6 code features be available in ES6 non-strict mode. But we don't prohibit them either. For any ES6 features that have no dependence on mode, like destructuring, we mandate that they be present in strict code, and we make them normative optional (the new Appendix B category) in non-strict code. Implementors are free to implement them or not in non-strict mode, but if they implement them, it must mean the same thing as the mandated meaning in strict code.

  • After Dave's posting I foresaw people wanting implicit opt-in with other constructs, e.g. classes or uses of `import' -- and voila, we're already there. But even with that, you still have to rely on explicit opt-in (via "use strict"?) for enabling some features elsewhere. As a result, we expect programmers to remember two separate, fairly random (for anybody not intimate with the history) lists of features that (1) require opt-in, and (2) imply opt-in. Style guides will probably suggest to put "use strict" on top of every Harmony program to escape the mess.

Every sane style guide will do so. And every linting tool should by default warn on the presence of any non-strict code. And every IDE should offer to make the code strict if it isn't already.

  • All this together (new features in old mode, implicit opt-in, explicit opt-in) makes it more subtle to see what piece of ES6 code is strict and which isn't. This is a disadvantage when reading code and a potential pitfall for refactoring (probably much worse than strict mode today).

The only sensible refactoring of non-strict code is to make it strict. All other refactorings should start with that one.

# Axel Rauschmayer (12 years ago)

On Jan 4, 2012, at 13:23 , Andreas Rossberg wrote:

Considering all that, I can't help feeling that having a separate mode is cleaner, simpler, and easier to use. I think it also has more potential for providing a robust foundation for future evolution of the language.

Taking a step back from modes: What will ES6 support for legacy browsers look like?

  • Wouldn’t you know per piece of code/file whether it is ES5 or ES6? And make that decision (all in or all out) per browser? I still don’t completely understand why/when it would be a mixed affair. I would write minimal setup code inline and then load a module as quickly as possible.

  • One possibility: several versions of each file: ES3, ES5, ES6. Are there ideas for how we could either statically keep the versions and let each browser load the appropriate one or how we could dynamically compile (either server-side or client-side, possibly including caching)? This multi-version scheme could even be applied to (the JS code embedded in) HTML files.

Axel

# Mark S. Miller (12 years ago)

On Wed, Jan 4, 2012 at 8:39 AM, Mark S. Miller <erights at google.com> wrote:

On Wed, Jan 4, 2012 at 4:23 AM, Andreas Rossberg <rossberg at google.com>wrote:

[...]

Here's an interesting compromise I consider perfectly reasonable. We don't mandate any ES6 code features be available in ES6 non-strict mode. But we don't prohibit them either. For any ES6 features that have no dependence on mode, like destructuring, we mandate that they be present in strict code, and we make them normative optional (the new Appendix B category) in non-strict code. Implementors are free to implement them or not in non-strict mode, but if they implement them, it must mean the same thing as the mandated meaning in strict code.

Except for nested named function definitions, which already have bizarre de facto behaviors in non-strict code that no one can fix. Perhaps there are more such conflicts in legacy non-standard features? If so, we probably need to exempt them as well.

# Allen Wirfs-Brock (12 years ago)

On Jan 4, 2012, at 4:23 AM, Andreas Rossberg wrote:

...

  • Despite the superficial "fewer modes" mantra, it actually doesn't make the language simpler, but more complicated: instead of defining the semantics for Harmony features only for strict-mode programs, we now have to define many features for both modes (and mixed uses of both modes). I.e., there are more syntactic and semantic combinations to worry about.

Yes, I'm already seeing this as I look at the working draft of the ES6 specification to see how I would have to modify it to support this new approach. Rather than working in a small number of discrete modes, each of which implies a specific set of environmental semantic rules, I have to consider pairwise interactions between features that span modes. Some initial issues I've had to look at are include the possibility of the presence of a local with scope when dealing with lexical declarations, impacts of being able to redeclare 'eval' or 'arguments', and interactions between formal parameter destructuring and non-strict mode arguments objects.

If the impact was only on the spec. work this might not be a big deal, but these sorts of mode-crossing feature interactions are also visible to implementors and ES programmers and add practical and conceptual complexity that they will have to deal with.

There certainly are features (for example, is and isnt) which seem to have minimal impact in any mode. But for others, there are complexities that go beyond just the syntactic compatibility issues. Modes (whether via explicit or implicit opt-in) seem to help reduce this complexity.

I think Dave has pushed us down a useful path, but I also think we need to carefully semantic interactions as well as syntactic compatibility issues for each feature that we consider adding to "non-strict" code. In some cases, it may make the language easier to understand and use if features are only available in "strict mode".

# Herby Vojčík (12 years ago)

Then, why not to put every feature on ES6 mode only and backporting them one by one, giving time for each backport to see if it really works and not putting effort to backport another one until the previous one is generally accepted as safe?

Herby

-----Pôvodná správa---

# Andreas Rossberg (12 years ago)

On 4 January 2012 17:39, Mark S. Miller <erights at google.com> wrote:

Here's an interesting compromise I consider perfectly reasonable. We don't mandate any ES6 code features be available in ES6 non-strict mode. But we don't prohibit them either. For any ES6 features that have no dependence on mode, like destructuring, we mandate that they be present in strict code, and we make them normative optional (the new Appendix B category) in non-strict code. Implementors are free to implement them or not in non-strict mode, but if they implement them, it must mean the same thing as the mandated meaning in strict code.

Hm, I don't understand what would be gained by that. You don't get rid of the spec complexity. And you risk reducing cross-browser compatibility, which is hardly a good thing. It doesn't really help the browser vendors either.

I think for anything but legacy, we should avoid normative optional.

  • After Dave's posting I foresaw people wanting implicit opt-in with other constructs, e.g. classes or uses of `import' -- and voila, we're already there. But even with that, you still have to rely on explicit opt-in (via "use strict"?) for enabling some features elsewhere. As a result, we expect programmers to remember two separate, fairly random (for anybody not intimate with the history) lists of features that (1) require opt-in, and (2) imply opt-in. Style guides will probably suggest to put "use strict" on top of every Harmony program to escape the mess.

Every sane style guide will do so. And every linting tool should by default warn on the presence of any non-strict code. And every IDE should offer to make the code strict if it isn't already.

Well, I had hoped that with ES6 we have more elegant ways of opting into Harmony than something like the "use strict" kludge. I also thought that that was part of Dave's motivation.

# Allen Wirfs-Brock (12 years ago)

On Jan 4, 2012, at 8:39 AM, Mark S. Miller wrote:

...

Here's an interesting compromise I consider perfectly reasonable. We don't mandate any ES6 code features be available in ES6 non-strict mode. But we don't prohibit them either. For any ES6 features that have no dependence on mode, like destructuring, we mandate that they be present in strict code, and we make them normative optional (the new Appendix B category) in non-strict code. Implementors are free to implement them or not in non-strict mode, but if they implement them, it must mean the same thing as the mandated meaning in strict code.

I don't think we every contemplated forbidding implementations from extending non-strict modes with versions of new features ES6 features.

However, your assumptions that destructuring has has no mode dependencies is wrong and a good example of why it is not so trivial to "include" it in non-strict code. Here are some of the dependencies I've already run into WRT formal parameter destructuring: using 'arguments' (or 'eval') in a formal parameter destructuring pattern or as a rest parameter - currently forbidden by strict mode effect of multiple use of the same name - currently forbidden in strict mode but currently allowed in non-strict mode interaction between new formal parameter forms and non-strict mode arguments object how is declaration instantiation order for non-strict functions impacted by parameter default value expressions can the temporal dead-zone rules related to parameter default value expression evaluation in strict mode also apply to non-strict functions

These tend to be subtle issues and the "right" answer is not always obvious. If different implementors decided to add the new formal parameter affordances to non-strict mode, without any guidance, they would likely come up with differing solutions to some of these issues and hence create imcompatabilities.

BTW, The simplest way to work around these issues, that I've found, would be to say that any function that uses any new formal parameter syntax is implicitly a strict mode function.

# Brendan Eich (12 years ago)

On Jan 3, 2012, at 11:29 PM, Mark S. Miller wrote:

On Tue, Jan 3, 2012 at 10:18 PM, Brendan Eich <brendan at mozilla.com> wrote: On Jan 3, 2012, at 9:52 PM, Mark S. Miller wrote:

A good example. Since the class proposal uses "private, public, static", by these principles, it would only be recognized in strict mode. If it appears in non-strict code, it would be a SyntaxError.

That's a choice. I'm asking why it's the best one. Just because class syntax might involve new reserved identifiers doesn't require strict mode. The introductory keyword is class and the new keywords are all in the braced body.

The idea is that the always-reserved 'class' keyword is opt-in enough -- no need for a "use strict"; string litearl (ignored by old browsers anyway!).

"opt-in enough", interesting. I'm not sure if this is what you meant, but it suggests a very attractive option I hadn't considered: treat "class", like "module", as yet another way to opt-in to strict mode. All code recursively inside a class would be strict.

Yes, that's what I meant.

Harmony > ES5-strict <!> ES5 (> means supserset, <!> means neither super- nor subset).

Any new Harmony syntax that presents no backward incompatibility and has a body opts the body into the new edition, a superset of ES5-strict.

For class, you're right. I really like the idea of having it be another way to opt-in to strict mode. What other constructs might join "class" and "module" as new strict-mode opt-ins?

As Dave mentioned, generators:

function* Fibonacci() { let [a, b] = [0, 1]; for (;;) { yield a; [a, b] = [b, a+b]; } }

Want to use "let" scoping outside a module? Again, just say "use strict";.

The string literal directive is meaningless in old browsers, and the other new syntax than let is guaranteed to fail with a syntax error in old engines, so what's the benefit of requiring the extra "use strinct"; again?

Huh? In ES5, the following code is legal:

var let = 88;

The following code is not:

function foo() { "use strict"; var let = 88; }

Consider instead these examples:

var let = 88; function foo() { "use strict"; let bar = 99; }

My point was pre-ES5 implementations don't respect "use strict", so the first line works while the second line fails with an early error due to unrecognized 'let'. As you note, ES5 implementations support the first and fail on the second with a different early error.

Now, if ES6 implementations extend "use strict" to enable let, then suddenly both work.

Therefore if authors face ES5 and ES6 implementations in the field, saying "use strict" is not enough to reliably use 'let'. True, ES5 implementations will fail with an early error -- just as they would on class or module declarations. But they'd fail on most 'let' declarations anyway, so why should "use strict" be required?

This is the question: if 'let' can be used reliably only in ES6 implementations and it fails fast in pre-ES6 implementations, why insist on, or educate users to add, "use strict" -- especially when that is not meaningful to pre-ES5 browsers, and the only difference between pre-ES5 and ES5 browsers is what kind of early error (perhaps just the message detail) is thrown?

I don't want to twist the grammar to turn "let" into a context sensitive reserved word so that somehow

"var let = 88;" and use of let-scoping can co-exist in the same mode. This seems like too high a price to pay simply to avoid any of the strict mode opt ins.

Agreed, that is a bridge too far and we've said so before. Gavin B. was asking again about it and I replied citing destructuring (let [x] = y; after another terminated statement) as one ambiguous case.

Any code under active maintenance should opt-in to strict somehow anyway.

You think so but developers are not doing it. Some have tried and been burned by concatenation without testing in ES5 implementations. Others don't like the performance effects (real but not necessarily biting them). "use strict" is not catching on, sorry to relate, and this is why I do not think we should yoke 'let' to it.

If we don't need to predicate 'let' on "use strict" to avoid backward incompatibility (other than in details of early error thrown), then we should not. The destructuring ambiguity remains, but perhaps there is a solution. Still thinking about this...

Non-strict mode is only for legacy that needs to keep working without any further human attention -- most of the web. This code already doesn't use let scoping, so what's the issue?

There are two issues:

  1. Needless "use strict" when new syntax (mostly or completely) is necessary and sufficient opt-in by itself. We agree this is the case for class and module -- also (I hope) for function*.

  2. Lack of "use strict" adoption means coupling to it too much raises risk of ES6 non-adoption.

Again, the criteria I care about is "practically backwards compatible". If it works to simply make "let" an unconditional keyword, then we should. On this one, I'm skeptical. It is a short three letter english word. If there are not many uses of it as a variable name, I would be very surprised.

Could be, I'm not sure how popular 'let' is in the field. Finding true positives would be conclusive, esp. in number.

On typeof, I am more than skeptical. I think this one is likely impossible without a third mode. But if the data says otherwise, we should go with the data.

This one needs some smart automated web code analysis. I'm stirring that pot.

But the "previously-illegal token sequence" approach complicates the grammar and readability in ways that are not worth it, just to avoid any of the means of opting in to strict, which all non-legacy code should do anyway. For modules, we'd be taking the "previously-illegal token sequence" approach in order to avoid introducing a third mode, which is so valuable that the grammar and readability complexities are worth it.

And for classes. And for generators? If yes for generators, then why not for comprehensions, rest/spread, destructuring (separate from the 'let' issue), for/of, and other new forms?

# Brendan Eich (12 years ago)

On Jan 4, 2012, at 4:12 AM, Andreas Rossberg wrote:

Your (3) seemed to say "use module" was something distinct from (and also implicit in, so a part but not the whole of what is declared by) module declaration syntax. It's not -- as proposed, it's an alternative to explicit anonymous module{...} bracketing syntax that translates the block or body enclosing the pragma to an anonymous module.

Yes, I understand that. My point was that you can reformulate all that, for (almost) equivalent effect, by saying that "use module" is not a module definition, but basically the same mode pragma as before,

Just to be clear: you mean |use version 6;| here by "mode pragma as before"?

except that it now is implicit with every module body.

Assuming you do, then there is a difference -- see below.

The only difference I can see with this description is its effect on the semantics of multi-part scripts.

Dave's proposal has

<script> window.foo = "hi"; </script> <script> use module; alert(foo); </script>

desugar to

<script> window.foo = "hi"; </script> <script> module { alert(foo); // no early error, runs and alerts "hi" } </script>

which is not the same as what we were thinking of with "no global object as top scope" enabled by a version pragma:

<script> window.foo = "hi"; </script> <script> use version 6; alert(foo); // early error here! </script>

# Brendan Eich (12 years ago)

On Jan 4, 2012, at 4:12 AM, Andreas Rossberg wrote:

On 3 January 2012 21:01, Brendan Eich <brendan at mozilla.com> wrote:

On Jan 3, 2012, at 1:29 AM, Andreas Rossberg wrote:

On 3 January 2012 07:21, Brendan Eich <brendan at mozilla.com> wrote:

The top level is hard. The only way to be sure is to use pure lexical scope (in Dave's proposal, use module {...}).

Ah, but wrapping into modules is incompatible with having multiple script parts.

I don't know what you mean. "Incompatible" in the sense that you cannot transform multiple scripts into multiple anonymous modules?

Yes.

That's a feature, in the proposal. You want the old behavior? Use <script> without |use module;| or |module {...}|. There's your backward compatibility :-P.

For multi-part scripts we need a way to switch the proper top-level into extended mode. Or should I not be able to write (the relevant bits of) a multi-part script in extended mode at all?

The proposal may have been unclear on this point: the top level would allow as much new syntax and semantics as can be tolerated backwards-compatibly. The hard cases are let, lexical scope in the free-variables-are-errors sense Dave described (extant globals at start of module body are imported), and any runtime shifts we want (completion reform, typeof null).

I suspect there may be other subtle issues, e.g., what about `const'

Not supported by IE, ever -- no intersection semantics (unlike block-contained functions).

and local functions? [I see, Allen mentioned that already.]

Right. Oh, the pain! Still I don't want to fall back to versioning if these are one of a few hard cases.

In any case, even if we allow more features in classic mode

Don't think of modes and you'll be grooving with the new proposal better ;-).

that still doesn't give you the ability to use all Harmony features for multi-part scripts. I think we need a proper story for this.

Agreed, but this desire is best satisfied by markup, e.g. that <meta> tag idea we've alluded to throughout this thread. Saying you want this doesn't undermine the new-syntax-as-opt-in proposal.

# Allen Wirfs-Brock (12 years ago)

On Jan 4, 2012, at 12:16 PM, Brendan Eich wrote:

...

Any new Harmony syntax that presents no backward incompatibility and has a body opts the body into the new edition, a superset of ES5-strict.

Including expression level bodies such a block lambda's or Dave's do { } expressions, if they are adopted??

# Brendan Eich (12 years ago)

On Jan 4, 2012, at 4:23 AM, Andreas Rossberg wrote:

  • Despite the superficial "fewer modes" mantra, it actually doesn't make the language simpler, but more complicated: instead of defining the semantics for Harmony features only for strict-mode programs, we now have to define many features for both modes (and mixed uses of both modes). I.e., there are more syntactic and semantic combinations to worry about.

Yes, this is a trade-off. It probably helps developers migrate, provided they choose sane combinations. For example, Allen (private correspondence) wondered if a function using the arguments object is evolved to have destructuring parameters:

function f({a,b}, c) { arguments[0] = {a:3,b:4}; return [a, b] } f({a:1,b:2}, 3);

I say anyone wanting deep aliasing (f returns [3, 4]) should suffer disappointment. I doubt developers want such deep aliasing, and no implementor does.

We don't get to clean the slate with JS, so making "mode walls" between language versions may help implementors (by which I include spec writers) but that's about it. Authors are not helped, and mode walls probably hurt (we hear).

I think we're better off working through the combinations, removing legacy (mis-)features as we go. For example, function f above, because it uses destructuring parameters, opts into the new language which is based on ES5-strict -- so there's no arguments aliasing at all!

The general idea would be that any new syntax that doesn't create backward compatibility problems on its face opts the containing (or following, in the case of new syntax in function head) body into strict mode. By body I probably mean function or program body and not just braced block.

  • Providing Harmony features in classic mode decreases the incentive for users to upgrade to strict mode. Is that a good thing?

See above -- Harmony > ES5-strict in the proposal and so users do upgrade to strict mode. They just don't have to sprinkle "use strict"; string literals expression statements around. That's a win.

  • After Dave's posting I foresaw people wanting implicit opt-in with other constructs, e.g. classes or uses of `import' -- and voila, we're already there. But even with that, you still have to rely on explicit opt-in (via "use strict"?) for enabling some features elsewhere.

Which features?

As a result, we expect programmers to remember two separate, fairly random (for anybody not intimate with the history) lists of features that (1) require opt-in, and (2) imply opt-in. Style guides will probably suggest to put "use strict" on top of every Harmony program to escape the mess.

If the new syntax implies strict mode in the containing body, then this goes away.

  • All this together (new features in old mode,

Nope. ;-)

implicit opt-in,

Yup.

explicit opt-in)

No -- "use strict"; the string literal expression-statement is meaningless pre-ES5, ES5-strict in ES5, and redundant in Harmony code.

(BTW I still think we want a real |use strict;| pragma, to choke old implementations.)

Now what do you say?

# Brendan Eich (12 years ago)

On Jan 4, 2012, at 12:50 PM, Allen Wirfs-Brock wrote:

On Jan 4, 2012, at 12:16 PM, Brendan Eich wrote:

...

Any new Harmony syntax that presents no backward incompatibility and has a body opts the body into the new edition, a superset of ES5-strict.

Including expression level bodies such a block lambda's or Dave's do { } expressions, if they are adopted??

Oh absolutely block lambda bodies are in Harmony > ES5-strict mode, you betcha!

Ditto generator bodies.

Class bodies, we covered. Module bodies -- in the o.p.

I like do {...} expressions, BTW -- tweeted about them today. They would be a wafer-thin (John Cleese pseudo-French accent) addition to ES.next.

# Brendan Eich (12 years ago)

On Jan 4, 2012, at 8:39 AM, Mark S. Miller wrote:

Here's an interesting compromise I consider perfectly reasonable. We don't mandate any ES6 code features be available in ES6 non-strict mode. But we don't prohibit them either. For any ES6 features that have no dependence on mode, like destructuring, we mandate that they be present in strict code, and we make them normative optional (the new Appendix B category) in non-strict code.

Too complicated. Why not just have "one JavaScript" (modulo ES5 strict mode, whether its selection was explicit or implicit)?

Implementors are free to implement them or not in non-strict mode, but if they implement them, it must mean the same thing as the mandated meaning in strict code.

We need interop on the web. If we let implementations vary as to whether, e.g., rest parameters require "use strict", then I predict the implementations that do not require "use strict" will win, and we'll have to spec normative non-optional anyway.

I'm very sure developers will not want "use strict"; as a requirement. That battle has been lost already with most developers (the war goes on, but let's not refight unnecessarily).

Anyway, predictions aside, I do not agree we should require "use strict" if the syntax speaks for itself.

My argument is not against strict mode (the basis of Harmony!) but rather against requiring "use strict"; directives to use new features that can be expressed without incompatible meaning shifts (only with guaranteed early errors in pre-Harmony implementations).

# Brendan Eich (12 years ago)

On Jan 4, 2012, at 9:35 AM, Allen Wirfs-Brock wrote:

On Jan 4, 2012, at 8:39 AM, Mark S. Miller wrote:

...

Here's an interesting compromise I consider perfectly reasonable. We don't mandate any ES6 code features be available in ES6 non-strict mode. But we don't prohibit them either. For any ES6 features that have no dependence on mode, like destructuring, we mandate that they be present in strict code, and we make them normative optional (the new Appendix B category) in non-strict code. Implementors are free to implement them or not in non-strict mode, but if they implement them, it must mean the same thing as the mandated meaning in strict code.

I don't think we every contemplated forbidding implementations from extending non-strict modes with versions of new features ES6 features.

However, your assumptions that destructuring has has no mode dependencies is wrong and a good example of why it is not so trivial to "include" it in non-strict code. Here are some of the dependencies I've already run into WRT formal parameter destructuring: using 'arguments' (or 'eval') in a formal parameter destructuring pattern or as a rest parameter - currently forbidden by strict mode

Destructuring opts into strict mode or rather its successor, ES.next. Harmony rolls on this way.

 effect of multiple use of the same name - currently forbidden in strict mode but currently allowed in non-strict mode

Not allowed by JS1.[78]* in SpiderMonkey or Rhino -- we prefigured ES5-strict on this, for sanity. When you use destructuring parameters in Firefox, you cannot have duplicate parameter names anywhere in the parameter list.

js> function f({a,b},a){}

typein:1: SyntaxError: duplicate argument is mixed with destructuring pattern: typein:1: function f({a,b},a){} typein:1: ............^ js> function f(b,{a,b}){}

typein:2: SyntaxError: duplicate argument is mixed with destructuring pattern: typein:2: function f(b,{a,b}){} typein:2: .................^ js> function f({a,a},c){}

typein:3: SyntaxError: duplicate argument is mixed with destructuring pattern: typein:3: function f({a,a},c){} typein:3: ...............^

 interaction between new formal parameter forms and non-strict mode arguments object

See above. Function is implicitly strict.

 how is declaration instantiation order for non-strict functions impacted by parameter default value expressions

See above, again.

 can the temporal dead-zone rules related to parameter default value expression evaluation in strict mode also apply to non-strict functions

Ditto.

These tend to be subtle issues and the "right" answer is not always obvious. If different implementors decided to add the new formal parameter affordances to non-strict mode, without any guidance, they would likely come up with differing solutions to some of these issues and hence create imcompatabilities.

We need a normative spec, of course! But we do not need to support new features in pre-strict-mode, indeed pre-ES6, combinations. New syntax is the opt-in!

BTW, The simplest way to work around these issues, that I've found, would be to say that any function that uses any new formal parameter syntax is implicitly a strict mode function.

Sorry, I should have read this far. I guess the proposal was unclear enough that people went down a bad path (at least you, probably Mark and Andreas). Sorry about that -- the intention (at least my view of it when reviewing Dave's draft proposal) was that new syntax opts in, and at a function granularity at least!

# Mark S. Miller (12 years ago)

On Wed, Jan 4, 2012 at 9:24 AM, Andreas Rossberg <rossberg at google.com>wrote:

On 4 January 2012 17:39, Mark S. Miller <erights at google.com> wrote:

[...]

Style guides will probably suggest to put "use strict" on top of every Harmony program to escape the mess.

Every sane style guide will do so. And every linting tool should by default warn on the presence of any non-strict code. And every IDE should offer to make the code strict if it isn't already.

Well, I had hoped that with ES6 we have more elegant ways of opting into Harmony than something like the "use strict" kludge. I also thought that that was part of Dave's motivation.

For style guides targeting ES6 only code, yes. They should simply advocate opting into strict mode in some way, and the preferred choice is unlikely to be "use strict";. For ES5, or for any code that must work on pre-ES6 browsers, "use strict"; is of course the only way to opt in to strict mode. I was thinking in terms of styles guides that anyone might write in the next three years.

# Mark S. Miller (12 years ago)

On Wed, Jan 4, 2012 at 9:35 AM, Allen Wirfs-Brock <allen at wirfs-brock.com>wrote:

On Jan 4, 2012, at 8:39 AM, Mark S. Miller wrote:

[...]

BTW, The simplest way to work around these issues, that I've found, would be to say that any function that uses any new formal parameter syntax is implicitly a strict mode function.

Good arguments -- both you and Andreas. I withdraw the suggestion that these be normative optional -- it indeed accomplished nothing useful. Instead, we simply do not forbid them in non-strict mode, which as you observe has always been the status quo.

I accept that destructuring is problematic for reasons I've overlooked. No doubt others are too. For each one, the first solutions we should reach for are 1) leave them out of strict mode, or 2) (as you suggest) make its presence an implicit opt-in to strict mode (which amusingly does effectively forbid it from appearing in non-strict code).

For destructuring, I think it is less suggestive notationally that it represents a boundary at which rules change, so I would rather simply omit it from non-strict mode. But either way would avoid these specification complexities, which I agree we should avoid.

# Mark S. Miller (12 years ago)

On Wed, Jan 4, 2012 at 12:16 PM, Brendan Eich <brendan at mozilla.com> wrote:

And for classes.

"class" is unconditionally reserved, so there's no issue.

And for generators?

I like the idea of generators as yet another strict opt-in, even though recognizing "function*" is technically an example of the "previously-illegal token sequence" approach. It does feel like an example of it readability wise. The eye rapidly learns to see "function*" as a keyword. The extra grammar complexity to recognize it is minimal.

If yes for generators, then why not for comprehensions, rest/spread, destructuring (separate from the 'let' issue), for/of, and other new forms?

I hadn't thought about comprehensions. As for rest/spread and destructuring, sure, if there's no complexity. However, Allen just pointed out that there would be complexity for allowing non-strict destructuring, so let's not. If we run across problems with the others, then probably not for those either. We've got enough important things to do that we shouldn't waste any time enhancing non-strict mode when doing so is non-trivial.

# Mark S. Miller (12 years ago)

I agree with most of your message, so only commenting on the remainder.

On Wed, Jan 4, 2012 at 12:52 PM, Brendan Eich <brendan at mozilla.com> wrote: [...]

  • After Dave's posting I foresaw people wanting implicit opt-in with other constructs, e.g. classes or uses of `import' -- and voila, we're already there. But even with that, you still have to rely on explicit opt-in (via "use strict"?) for enabling some features elsewhere.

Which features?

Lexical scoping (top level aside of course). Encapsulated functions. Sane arguments. Poisoning of .caller, .callee, .arguments. Ability to write code that continues to run on old browsers and to run sanely on old ES5 browsers. Throw errors on failed assignment rather than silently continuing as if everything is fine. Throw exception on a failed delete. Throw on assigning to an undefined variable rather than having misspelling silently create a new global. Preventing an eval from corrupting its caller's scope. Need I go on? Compared to ES5 strict, ES5 non-strict is insane.

Regarding adoption of strict, another audience we should keep in mind is people newly learning JavaScript. Would you rather teach them to say "use strict"; or teach them to program in a language without sane scope rules or encapsulated functions, and with silent errors?

No -- "use strict"; the string literal expression-statement is meaningless

pre-ES5, ES5-strict in ES5, and redundant in Harmony code.

I don't see how it can be redundant in harmony code. Say I write a script, intending it to be harmony, but which doesn't begin with "class", "module", "function*", or whatever else we decide is a new opt-in. But I want the code above the first, say "module", to still obey harmony rules rather than non-strict rules. I would still need to say something else at the top of the script to ensure this.

(BTW I still think we want a real |use strict;| pragma, to choke old implementations.)

Yes. Crock suggested this in the old ES5 days and I think it is still a good idea:

"use strict"; // still runs on old browsers, but non-strictly

use strict;  // causes an early error on old browsers.

Had we adopting it into ES5, it would have this meaning clearly. It may still be a good idea, but consider the new complexity. Now the second form also causes an early error on old ES5 browsers, where the script might otherwise have been able to run strictly.

# Mark S. Miller (12 years ago)

On Wed, Jan 4, 2012 at 1:00 PM, Brendan Eich <brendan at mozilla.com> wrote:

Anyway, predictions aside, I do not agree we should require "use strict" if the syntax speaks for itself.

Is anyone saying that we should require this? I'm not. This sub-thread started with Dave's "module" as opt-in suggestion and you and I agreed earlier that "class" would also opt-in, so I'm not sure what you're arguing against.

My argument is not against strict mode (the basis of Harmony!) but rather against requiring "use strict"; directives to use new features that can be expressed without incompatible meaning shifts (only with guaranteed early errors in pre-Harmony implementations).

We're already agreeing that there should be multiple ways to opt-in to strict mode, with "function*" joining this happy set.

# Mark S. Miller (12 years ago)

On Wed, Jan 4, 2012 at 1:07 PM, Brendan Eich <brendan at mozilla.com> wrote: [...]

See above. Function is implicitly strict.

I looked about and didn't see whatever I was supposed to notice.

I would love for Function to be implicitly strict. But I have no idea how to do that without breaking the web. Function is a value on the heap shared by legacy non-strict scripts and newfangled Harmony strict scripts. So far we've been careful that the opt-in switches only per-code properties, not per-heap properties. How would this work.

We need a normative spec, of course! But we do not need to support new features in pre-strict-mode, indeed pre-ES6, combinations. New syntax is the opt-in!

If all new syntax causes opt-in, that does effectively bar non-strict mode from being extended with non-strict versions of these primitives. That's a fine outcome, but I'm surprised to hear it.

The problem I see is that the occurrence of some of these new features, like a light use of destructuring, may not be notationally obvious to readers.

# Axel Rauschmayer (12 years ago)

I think I would prefer a simpler per-file approach. In light of having to do something like this again for ECMAScript.next.next how about the following?

First line:

  • "use strict"; // before ES5: ignore; ES5: ES5.strict
  • use es6; // ES6: ES6
  • module <ident>? { is a synonym for use es6;

With JS language versions being such a prominent issue on the web, I wouldn’t mind seeing at first glance what kind of code I am looking at.

My idea might be completely off, but whatever the final solution, it should be dead-simple to explain.

# Brendan Eich (12 years ago)

On Jan 4, 2012, at 4:12 PM, "Mark S. Miller" <erights at google.com> wrote:

The problem I see is that the occurrence of some of these new features, like a light use of destructuring, may not be notationally obvious to readers.

Maybe not, but for such users, strict mode may not be obviously a problem either. This may well be the shortest path to migration and adoption, esp. compared to version= lock-in.

# Brendan Eich (12 years ago)

How does |use es6| help for es7? It doesn't, especially if es7 has runtime-incompatible changes (i.e., is not a superset).

Better to dispense with modes or ordered versions altogether, which is the key idea of the proposal.

/be

Sent from my iPad

# Mark S. Miller (12 years ago)

In your suggestion, when an occurrence of destructuring (or any other new syntax) is seen, what unit of code is then opting in to strict? Would it be the nearest enclosing function, module, class, generator, or Program? I think I'm warming to the idea.

I think having the opt-in unit be the destructuring pattern and everything recursively contained within it would be a bad idea. In this regard, destructuring is a different category of opt-in by new syntax than is module, class, and generator.

# Claus Reinke (12 years ago)

Considering all that, I can't help feeling that having a separate mode is cleaner, simpler, and easier to use. I think it also has more potential for providing a robust foundation for future evolution of the language.

This last point -language evolution- is something that Haskellers have quite a bit of experience with, and I've tried to raise the issue of feature-based language versioning here earlier [1,2].

In Haskell's beginning, there was the single language standard, and every implementation had a single flag for enabling all of its additional or experimental features (beyond the standard). But implementations come and go, and even the de-facto standard of most-widely-used implementation has changed over time. Even when several implementations agreed on a feature, they might disagree on some details (eg, a proven safe but restrictive variant versus a possibly unsafe but pragmatically better version). And the language committees have been notoriously slow in adopting experimental features into the language standard.

So the situation became untenable, and the solution was feature-based language versioning (based on pragmas), in addition to named standards:

By default, code is interpreted according to the most recent language standard but, for stability, code can explicitly opt into a specific version of the standard. Up to here, that is similar to Javascript, only that the version can be specified in code pragmas as well.

On top of this default, language extensions can be selected individually, via pragmas. It is then up to the implementation to recognize whether or not the selected set of language extensions is supported. As in JS, language extensions tend to have recognizable syntax, too, so implementations can also warn intelligently about attempts to use features that have not been selected (or about conflicts that arise because of features that have been selected, such as stolen syntax).

Useful language extensions can be adopted by other implementations (at which point they need to be specified, not just implemented), and later moved into one of the next language standards. Agreeing on the next language standard becomes more of gather-successful-features than redesign-everything-from-scratch, with shorter language update cycles as well. A feature missing a language update deadline becomes less of an issue, as it is still available as an extension. Features that do not work out in practice are easier to remove, as they have their own pragma (not mixed into a numeric language version).

In the current phase of JS evolution, with an evolving new standard and partial implementations of new features, it would be especially helpful to identify which features a piece of code depends on and which features a given implementation supports. Being able to opt in to new features individually also puts a bound on upgrade work (e.g. opt into let, but not yield, or vice-versa), and helps to ensure that coders are aware of changes (if I opt into let, I expect let to be special; if I opt into ES.next because I want yield, I might be surprised by changes to let and scope and typeof and ..).

Just a thought.. Claus

[1] Should ES begin to standardise pragmas? esdiscuss/2011-April/013791 [2] feature-based and compartment-based language versioning esdiscuss/2011-July/015755

# Andreas Rossberg (12 years ago)

On 4 January 2012 21:27, Brendan Eich <brendan at mozilla.com> wrote:

On Jan 4, 2012, at 4:12 AM, Andreas Rossberg wrote:

Your (3) seemed to say "use module" was something distinct from (and also implicit in, so a part but not the whole of what is declared by) module declaration syntax. It's not -- as proposed, it's an alternative to explicit anonymous module{...} bracketing syntax that translates the block or body enclosing the pragma to an anonymous module.

Yes, I understand that. My point was that you can reformulate all that, for (almost) equivalent effect, by saying that "use module" is not a module definition, but basically the same mode pragma as before,

Just to be clear: you mean |use version 6;| here by "mode pragma as before"?

Yes.

The only difference I can see with this description is its effect on the semantics of multi-part scripts.

Dave's proposal has

<script>     window.foo = "hi";   </script>   <script>     use module;     alert(foo);   </script>

desugar to

<script>     window.foo = "hi";   </script>   <script>     module {       alert(foo);   // no early error, runs and alerts "hi"     }   </script>

which is not the same as what we were thinking of with "no global object as top scope" enabled by a version pragma:

<script>     window.foo = "hi";   </script>   <script>     use version 6;     alert(foo);     // early error here!   </script>

Right. That's (part of) what I was alluding to with my above remark. ;)

# Andreas Rossberg (12 years ago)

On 4 January 2012 21:30, Brendan Eich <brendan at mozilla.com> wrote:

In any case, even if we allow more features in classic mode

Don't think of modes and you'll be grooving with the new proposal better ;-).

Well, I'd say "syntactic context" is just a euphemism. Or maybe the other way round...

# Andreas Rossberg (12 years ago)

On 4 January 2012 21:52, Brendan Eich <brendan at mozilla.com> wrote:

On Jan 4, 2012, at 4:23 AM, Andreas Rossberg wrote:

  • Despite the superficial "fewer modes" mantra, it actually doesn't make the language simpler, but more complicated: instead of defining the semantics for Harmony features only for strict-mode programs, we now have to define many features for both modes (and mixed uses of both modes). I.e., there are more syntactic and semantic combinations to worry about.

Yes, this is a trade-off. It probably helps developers migrate, provided they choose sane combinations. For example, Allen (private correspondence) wondered if a function using the arguments object is evolved to have destructuring parameters:

function f({a,b}, c) { arguments[0] = {a:3,b:4}; return [a, b] }  f({a:1,b:2}, 3);

I say anyone wanting deep aliasing (f returns [3, 4]) should suffer disappointment. I doubt developers want such deep aliasing, and no implementor does.

Whoa, yeah, please let's not go there.

  • All this together (new features in old mode,

Nope. ;-)

implicit opt-in,

Yup.

explicit opt-in)

No -- "use strict"; the string literal expression-statement is meaningless pre-ES5, ES5-strict in ES5, and redundant in Harmony code.

You said earlier that we best rely on the meta tag for the script toplevel, which isn't exactly a No. ;)

Now what do you say?

Sorry, I still think that a growing set of subtle implicit rules for activating subtle semantic changes on a fine-grained level is far more confusing (and error-prone!) than helpful. In all sorts of ways.

I'm also concerned about the 3 and a half language modes that might result. With Dave's original proposal at least, the only opt-in was on module level. That precluded a number of highly undesirable combinations, e.g. extended mode nested into a "with" statement. Allen gave a couple of good examples we also ran into when trying to implement block scoping liberally in V8 -- in the end, we concluded that it doesn't make sense to allow opting into extended mode locally.

# Axel Rauschmayer (12 years ago)

On Jan 5, 2012, at 1:56 , Brendan Eich wrote:

Better to dispense with modes or ordered versions altogether, which is the key idea of the proposal.

So, simplified, the story is:

  • ES6 is a superset of ES5.strict.
  • Therefore, as soon as we have ES6, ES5.strict code bases automatically become ES6 code bases.
  • ES{3,5}.non-strict is neither a subset nor a superset of ES6, it has to be handled differently.

If these modes (strict versus non-strict) exist, then I would want a per-file marker for strict code:

  • "use strict";
  • use strict;
  • module {

Compared to languages such as Java, JavaScript additionally faces the challenge that a developer can’t control what language version is implemented by the browser. How will that be handled? Sketching a solution would provide a more complete picture of how to migrate to ES6.

# Allen Wirfs-Brock (12 years ago)

On Jan 4, 2012, at 11:38 PM, Mark S. Miller wrote:

In your suggestion, when an occurrence of destructuring (or any other new syntax) is seen, what unit of code is then opting in to strict? Would it be the nearest enclosing function, module, class, generator, or Program? I think I'm warming to the idea.

I think having the opt-in unit be the destructuring pattern and everything recursively contained within it would be a bad idea. In this regard, destructuring is a different category of opt-in by new syntax than is module, class, and generator.

This first came up in the context of formal parameters that use destructuring (or default value initializers, or rest). In that case, the idea was that this would imply that the function with those parameters is a "strict" function.

Whether the use of destructuring (or rest/spread) in declarations or expressions within the body of the function also implies a "strict" function is potentially a separate issue. However, I think we need to keep things as simple and consistent as possible so on that basis perhaps they should also imply a strict function. In neither case, were we thinking of restricting the strictness to just the destructuring pattern itself.

A slightly different approach to this might be to say that the use of any new syntax implies that the immediately surrounding function or program is in strict mode. In other words, the default is strict mode unless the code exclusively uses ES5 syntactic constructs.

# Brendan Eich (12 years ago)

On Jan 5, 2012, at 6:31 AM, Andreas Rossberg wrote:

explicit opt-in)

No -- "use strict"; the string literal expression-statement is meaningless pre-ES5, ES5-strict in ES5, and redundant in Harmony code.

You said earlier that we best rely on the meta tag for the script toplevel, which isn't exactly a No. ;)

Trying to avoid scope creep. Also not dismissing RFC4329 ;version=6 parameter setting -- that works to hide new code from old browsers (I believe back to IE8 -- perhaps not the geriatric browsers).

Now what do you say?

Sorry, I still think that a growing set of subtle implicit rules for activating subtle semantic changes

Not changes, new semantics for new syntax. The fingers of fate may allow completion reform (Mark and I think so). The interactions are not that hard, and making a mode-wall doesn't avoid solving all of them -- you still may have runtime (heap-based) interactions between old and new modes.

We did JS1.7 this way, BTW. You had to opt in to get 'let' and 'yield', but that was for the compiler. We did not hold back other features, and there were no runtime (post-compile) version checks:

brendan-eichs-computer-3:src.old brendaneich$ grep VERSION_1_7 *.c jsapi.c: {JSVERSION_1_7, "1.7"}, jsparse.c: || (JSVERSION_NUMBER(cx) == JSVERSION_1_7 && jsparse.c: ((JSVERSION_NUMBER(cx) == JSVERSION_1_7 && jsparse.c: if (JSVERSION_NUMBER(cx) == JSVERSION_1_7) { jsparse.c: if (JSVERSION_NUMBER(cx) == JSVERSION_1_7) {

(jsapi.c is just a string <-> enumerator mapping table; jsparse.c shows the compiler checks.)

on a fine-grained level is far more confusing (and error-prone!) than helpful. In all sorts of ways.

Our experience was that adding new features to the default version where there was no backward incompatibility was not confusing. We've been doing this since 2006. Not to say all the particulars are right, or that we anticipated all the combinations of ES5-strict and Harmony, of course! But I think you protest too much without evidence.

I'm also concerned about the 3 and a half language modes that might result. With Dave's original proposal at least, the only opt-in was on module level.

Dave mentioned generators, IIRC. We were thinking of classes too (talked about it privately).

That precluded a number of highly undesirable combinations, e.g. extended mode nested into a "with" statement.

You can "use strict"; in a with statement's body block. But see below, I agree the opt in has to be "chunky", and is in the (not perfectly clear, complete, etc.) proposal for "ES6 doesn't need [version] opt-in".

Allen gave a couple of good examples we also ran into when trying to implement block scoping liberally in V8 -- in the end, we concluded that it doesn't make sense to allow opting into extended mode locally.

Yes, this is one place where during the ES4 period, we made 'let' at top level of program and function bodies equate to 'var', which is no good (arguments aliasing, for one). But lesson learned.

The idea is to opt in at least the enclosing function, if not the enclosing Program (non-terminal, so <script> element content) when unproblematic new syntax is parsed.

# Brendan Eich (12 years ago)

On Jan 5, 2012, at 9:30 AM, Allen Wirfs-Brock wrote:

A slightly different approach to this might be to say that the use of any new syntax implies that the immediately surrounding function or program is in strict mode.

I'm favoring program currently, even if the new syntax is used in just one (of N) functions declared or expressed in the program. We could be narrow and impute Harmony opt-in only to the nearest containing function (or failing that, program). That might help incremental migration, indeed, and it seems a more locally "reasonable" rule.

But it may be that developers would benefit more from in-for-a-penny-in-for-a-pound. Hard to say without user testing.

In other words, the default is strict mode unless the code exclusively uses ES5 syntactic constructs.

Right, but we need to settle on the unit or scale of "the code".

# Brendan Eich (12 years ago)

On Jan 5, 2012, at 11:10 AM, Brendan Eich wrote:

That precluded a number of highly undesirable combinations, e.g. extended mode nested into a "with" statement.

You can "use strict"; in a with statement's body block.

Sorry to be unclear, I meant examples such as this:

js> with ({p:1}) { print(function () {"use strict"; return p;}()); }

1

The strict function expression still has to cope with the nasty object scope environment on its outside.

# Brendan Eich (12 years ago)

On Jan 5, 2012, at 8:30 AM, Axel Rauschmayer wrote:

On Jan 5, 2012, at 1:56 , Brendan Eich wrote:

Better to dispense with modes or ordered versions altogether, which is the key idea of the proposal.

So, simplified, the story is:

  • ES6 is a superset of ES5.strict.

That's always been promised.

  • ES{3,5}.non-strict is neither a subset nor a superset of ES6, it has to be handled differently.

If these modes (strict versus non-strict) exist, then I would want a per-file marker for strict code:

  • "use strict";

This doesn't mean Harmony, though. Not now, in ES5 implementations -- it means only ES5-strict. And not in dowrev impls, where it means nothing (and semantics differ at runtime).

  • use strict;

This is a good idea but it was not proposed by Dave to mean Harmony opt-in. Rather, |use module;| was, as a way to avoid bracing and indenting a top level hunk of code in an anonymous module {...} declaration.

  • module {

Compared to languages such as Java, JavaScript additionally faces the challenge that a developer can’t control what language version is implemented by the browser. How will that be handled? Sketching a solution would provide a more complete picture of how to migrate to ES6.

APIs can be object-detected. New syntax requires autoconf-style eval/Function tests. All doable, nothing mysterious, some try/catch pain required.

I rather expect a boot-loader script will use more coarse-grained means of deciding what to fetch. We had proposed a way to reflect on the maximum supported version (ECMASCRIPT_VERSION, IIRC) but that was unsatisfying. More to do here.

# Allen Wirfs-Brock (12 years ago)

Here is a possible set of rules for implicitly assigning ES5 or ES6 semantics to a Program unit

In an "ES6" implementation, all constructs that can occur in a valid program fit into one of these categories: ES6-only: The construct is based upon syntax or static semantics rules that only exist in ES6. For example, a destructuring pattern ES5-only: The construct is based upon syntax that or static semantics rules that that are not in ES6. For example, use of a with statement t. ES5&ES6: The construct has identical semantics in both ES5 and ES6. ES5~EAS6: The construct has identical syntax and static semantics in both ES5 and ES6, but differing semantics. For example, accessing a formal parameter after an assignment to the corresponding element of the function's arguments object.

We can then use the following state machines to describes the processing of a Program based upon the occurrence for these feature categories. Initially start in State 5&6:

State 5&6 if current construct is in ES5&ES6, process using intersection semantics, then goto State 5&6 if current construct is in ES5~ES6, process using ES5 semantics then, goto State Compat5 if current construct is in ES5-only, process using ES5 semantics then goto State ES5 if current construct is in ES6-only, process using ES6 semantics then goto State ES6

State ES5 if current construct is in ES5&ES6, process using intersection semantics, then goto State ES5 if current construct is in ES5~ES6, process using ES5 semantics, then goto State ES5 if current construct is in ES5-only, process using ES5 semantics then goto State ES5 if current construct is in ES6-only, terminate with Error: invalid combination of ES5 and ES6 features

State ES6 if current construct is in ES5&ES6, process using intersection semantics, then goto State ES6 if current construct is in ES5~ES6, process using ES6 semantics, then goto State ES6 if current construct is in ES5-only, terminate with Error: invalid combination of ES5 and ES6 features if current construct is in ES6-only, process using ES6 semantics then goto State ES6

State Compat5 (or have an analogous Compat6 state, and restart to State ES5 when necessary) if current construct is in ES5&ES6, process using intersection semantics, then goto State Compat5 if current construct is in ES5~ES6, process using ES5 semantics then, goto State Compat5 if current construct is in ES5-only, process using ES5 semantics then goto State ES5 if current construct is in ES6-only, abort current compilation and restart from beginning, starting in State ES6

Basically using any ES6 features makes it an ES6 program. Using any ES5-only feature makes it an ES5 program. Combining ES5-only and ES6 features results in an invalid program. If a Program can not be explicitly identified as either ES5 or ES6, it is treated as an ES5 program.

If you want to explicitly force ES6 processing put a: let ES6; at the top of the source file.

If you want to explicitly force ES5 processing put a: with (5); at the top of the source file

# Mark S. Miller (12 years ago)

if ES5 had only one mode, I'd understand this. But I thought we were trying to arrive at two modes, ES6 non-strict, to be backwards compat with ES5-non-strict, and ES6 strict, to be backwards compat with ES5-strict. I am perfectly happy to call ES6 non-strict "ES3", since ES5-non-strict really exists to be an ES3 compatibility mode, and was constrained to be backwards compatible from ES3. Likewise, I am happy to call the new ES6 strict simply "ES6".

Concretely, I am confused how your transition diagram is supposed to handle "use strict";. Reading your state machine literally, since "use strict"; is accepted by ES5, if it is accepted by ES6 (as I think we all agree it would be), then it would leave us in state ES5&ES6. Were you two base categories "ES6 non-strict" (or ES3) and "ES6 strict" (or ES6), then a "use strict"; would put us in your ES6 strict (or ES6) category, which is what I would have expected.

# Allen Wirfs-Brock (12 years ago)

On Jan 5, 2012, at 8:24 PM, Mark S. Miller wrote:

Hi Allen, if ES5 had only one mode, I'd understand this. But I thought we were trying to arrive at two modes, ES6 non-strict, to be backwards compat with ES5-non-strict, and ES6 strict, to be backwards compat with ES5-strict. I am perfectly happy to call ES6 non-strict "ES3", since ES5-non-strict really exists to be an ES3 compatibility mode, and was constrained to be backwards compatible from ES3. Likewise, I am happy to call the new ES6 strict simply "ES6".

When I talked about "ES5" in my original post I meant full ES5 including both strict and non-strict modes. Since ES6 is supposed to be a strict super set of ES5 strict mode, anything in the ES5-only category must be an exclusively non-strict mode feature (for example, with). I suppose you could call that category "ES3" but I decided to label it "ES5-only" to keep things focused on differences between ES5 and ES5.

Concretely, I am confused how your transition diagram is supposed to handle "use strict";. Reading your state machine literally, since "use strict"; is accepted by ES5, if it is accepted by ES6 (as I think we all agree it would be), then it would leave us in state ES5&ES6. Were you two base categories "ES6 non-strict" (or ES3) and "ES6 strict" (or ES6), then a "use strict"; would put us in your ES6 strict (or ES6) category, which is what I would have expected.

Yes, "use strict" is in the ES5&ES6 category. A ES5 completely strict mode program would start in State 5&6 and stay in that state for its entire "compilation". Only encountering use of a new ES6 feature would case a transition to State ES6. Looking at it another way, Both State ES5 and State 5&6 include support for both strict and non-strict ES5 code. State ES6 only allows strict code.

# Mark S. Miller (12 years ago)

On Thu, Jan 5, 2012 at 10:13 PM, Allen Wirfs-Brock <allen at wirfs-brock.com>wrote:

On Jan 5, 2012, at 8:24 PM, Mark S. Miller wrote:

Hi Allen, if ES5 had only one mode, I'd understand this. But I thought we were trying to arrive at two modes, ES6 non-strict, to be backwards compat with ES5-non-strict, and ES6 strict, to be backwards compat with ES5-strict. I am perfectly happy to call ES6 non-strict "ES3", since ES5-non-strict really exists to be an ES3 compatibility mode, and was constrained to be backwards compatible from ES3. Likewise, I am happy to call the new ES6 strict simply "ES6".

When I talked about "ES5" in my original post I meant full ES5 including both strict and non-strict modes. Since ES6 is supposed to be a strict super set of ES5 strict mode, anything in the ES5-only category must be an exclusively non-strict mode feature (for example, with). I suppose you could call that category "ES3" but I decided to label it "ES5-only" to keep things focused on differences between ES5 and ES5.

Concretely, I am confused how your transition diagram is supposed to handle "use strict";. Reading your state machine literally, since "use strict"; is accepted by ES5, if it is accepted by ES6 (as I think we all agree it would be), then it would leave us in state ES5&ES6. Were you two base categories "ES6 non-strict" (or ES3) and "ES6 strict" (or ES6), then a "use strict"; would put us in your ES6 strict (or ES6) category, which is what I would have expected.

Yes, "use strict" is in the ES5&ES6 category. A ES5 completely strict mode program would start in State 5&6 and stay in that state for its entire "compilation". Only encountering use of a new ES6 feature would case a transition to State ES6. Looking at it another way, Both State ES5 and State 5&6 include support for both strict and non-strict ES5 code. State ES6 only allows strict code.

Them either I still don't understand, this proposal is broken, or we're talking about three modes. Take the following three programs:

1) program using only ES3 features and no "use strict";

2) program using only ES5 strict features and saying "use strict";

3) program using ES6-only features.

Do these three programs operate in three different modes? If not, do #1 and .#2 operate in the same mode, or do #2 and #3 operate in the same mode?

Putting #1 and #2 into the same mode breaks ES5 code. So to avoid three modes, my conclusion is that #2 and #3 must be in the same mode. But that does not seem to be what you're saying. What am I misunderstanding?

# Luke Hoban (12 years ago)

Jumping in late, and possibly repeating some already covered ground, but this is clearly an important topic.

Looking at all the proposals on these threads, I have to throw support behind Mark's proposal below, or a close variant of it as discussed below and in some of the other branches (I think this is close to what Brendan has been advocating as well?). Let me try to motivate this from a somewhat different direction. Wall of text below - but the punchline is:

Let's avoid versioning by making (almost) no breaking changes.

Versioning

There are two goals to versioning - (1) allow breaking changes that code can explicitly opt-in to and (2) support reasonable forward compatibility of new code running on old runtimes.

The original plan for ES6 was to support (1) and (2) via a versioned script tag. This allowed breaking changes because it was explicit opt in, and supported forward compatibility to the extent that there was graceful degradation of behaviour on old runtimes (skipping the script block).

Versioned script tags are onerous though, and folks are rightly looking for better options.

The better option is to not do versioning at all. To do that, you need to give up on (1) and (2). That is - no breaking changes (or at least no practically significant ones), and no 1st class support for forward compatibility.

Forward compatibility

The forward compatibility point in favor of a versioned script tag was never particularly strong anyway. A versioned script tag is ignored on most down-rev browsers, but it is still hard to construct programs using new syntax that behave pleasantly on those browsers, because the granularity of feature detection is at the script-block level, not the application-feature level. To the extent that apps want to use new syntax and still work on older browsers, they can almost as easily just accept the early errors from their <script> blocks and detect whether they got past early errors successfully in a later block in the same way they would with a versioned script tag. So giving up the forward compatibility support doesn't appear to sacrifice much.

Breaking changes

Breaking changes are different. There are quite a lot of breaking changes in the current ES6 proposals. This is somewhat unsurprising, because for the history of development of ES6 proposals to date, the assumption has been that ES6 will be explicit opt-in for syntax. So we accepted all sorts of breaking changes based on that barrier to entry, and developer expectations associated with explicit opt-in. But now we want to not do versioning for ES6. So the bar for breaking changes should necessarily be much, much higher.

As Mark noted below, this higher bar should effectively disqualify "typeof null", completion reform (if the break is significant enough to matter in practice), and removal of the global object from the scope chain. I believe these are reasonable things to give up in favor of not having to version.

The next big breaking change is implicit strict mode in ES6. Because it was designed with an explicit opt-in in mind, strict mode has lots of breaking changes which we can never offer unversioned. As with the above, we should give this up. The result is effectively what Mark describes - "use strict" remains the opt-in to the strict mode breaking changes.

The last set of breaking changes are reserved words. As Mark notes, in strict mode many of the new keywords are already reserved, and so strict mode can allow "let" et al.

However, I think we can go further than that (as Brendan and Allen I think also pointed out?). In Dave's original proposal, he showed how "module" can be introduced as a contextual keyword. I believe we can do this for many more of the features as well. And for those that can't, we should look for potential alternative syntax that can be introduced as contextual keywords.

Classes were raised as a counter example here in a branch of the thread. But it seems they can be handled nearly identically to "module" - as a contextual keyword with appropriate restricted productions.

let[x]=[5] was also raised as well on one of these threads, as a reason why "let" cannot be made contextual. This one does seem harder to get out of - but given how corner case this break is, we could allow this to be resolved as a let binding and accept the very minor breaking change in just this corner case, treating "let" as a contextual keyword elsewhere.

So the extension to Mark's proposal is that most (possibly all) ES6 syntax is also allowed without "use strict", but with the design modified wherever necessary to be (almost) fully backward compatible.

Why 'implicit explicit opt-in' doesn't seem reasonable

The prevalent alternatives presented in this thread are variations of "implicit explicit opt-in", where use of some new syntax causes some part of the code inside or outside of it to start behaving differently (breaking changes). I think in practice this will be very confusing. Take this:

var x = typeof null; module { var y = typeof null; x == y // false! }

This is a refactoring hazard, much harder to find by code inspection than "use strict", and just plain confusing. Alternatives like having a "let" inside a function body automatically opt the body into the sort of behaviour above feel ever more magic, and very hard to reason about thoroughly.

Moreover, these breaking changes all come at conceptual cost for JavaScript developers. While we may think we are making things better by "fixing" typeof, we are actually just making the section on typeof in Doug's slide deck longer - he needs to describe both behaviours, and when to expect each. We already see this with strict mode - the answer to "what does this JavaScript code do?" now often has to be answered by "well, if it's in strict mode... otherwise...", instead of a direct simple (even if not desired) answer. It is even worse in these "implicit explicit opt-in" models. In those cases, the answer becomes "well, if it's inside a 'module', or in strict mode, or inside a function which contains anywhere inside it a 'let', or...".

Breaking changes, especially if opted into through "implicit explicit opt-in" add to the total complexity of the language. Moreover, if there aren't breaking changes, there is no need for "opt-in" at all.

Conclusion

Let's avoid versioning by making (almost) no breaking changes.

Luke

From: es-discuss-bounces at mozilla.org [mailto:es-discuss-bounces at mozilla.org] On Behalf Of Mark S. Miller Sent: Tuesday, January 03, 2012 1:24 PM To: Allen Wirfs-Brock Cc: Brendan Eich; es-discuss Steen Subject: Re: ES6 doesn't need opt-in

                       Just Two Modes

This is a long thread and I've been completely busy with other things so have not had time to do more than skim. So please understand if the post below misses some context. The following is a summary of some principles that Dave and just agreed to in a verbal conversation, but he hasn't had the chance to look at the following text before I send it, so it may not quite speak for our agreement -- I've substantially elaborated it since the text that Dave and I looked at together. Dave introduced this thread with the slogan "just one JavaScript", so I'll introduce the following with the (much less catchy) slogan "just two modes".

  • ES5's strict vs non-strict distinction remains the only mode distinction. ES6 thus has only the same two modes.

  • ES6 non-strict mode must be practically upwards compatible from ES5 non-strict mode.

  • ES6 strict mode must be practically upwards compatible from ES5 strict mode.

  • In ES6, one can opt-in to strict mode in at least the following two ways:

    "use strict"; // exactly as in ES5

or

module <ident>? {

in statement context. In other words, exactly as ES5 may begin strict mode at a function boundary to apply to everything recursively contained lexically within the function, in ES6 in addition, strict mode may also begin at a module boundary and apply to everything recursively within the module. Code recursively contained within a module is always strict; there's no way to write non-strict code within a module. But a module, like a function, may be embedded within a non-strict context.

  • Code that contains such a module construct may run on an ES5 system or may cause an early SyntaxError, depending on whether this ES5 implementation has been extended to recognize the module construct. An ES6 system must of course recognize the module construct. Thus, modules, as well as most other features of ES6, may be deployed incrementally, just as many features of ES5 were deployed incrementally in the transition from ES3 to ES5.

  • We give up typeof reform.

  • We do completion reform only if we judge it to be practically upward compatible, with a dispensation to ES5 implementations to implement it without penalty of being non-conformant. (Dave and I both expect it will in fact be practically upwards compatible.)

  • As with completion reform, if there are other cleanups we can make to ES5 that is practically upwards compatible, e.g., whose only incompatibility is with test262, we can consider these for ES6 and absolve ES5 systems that adopt these cleanups.

  • We obtain a clean top level scope only by using loaders, which is increasingly how I've been thinking of SES anyway.

  • The identifiers that are reserved in ES5 only in strict mode are:

    implements, interface, let, package, private, protected, public, static, yield ES6 features that use these keywords are available only in strict mode. Because ES5 reserved them, this is fully upwards compatible with ES5. For other ES6 features that do not depend on these keywords, whether or not they must also be available in ES6 non-strict code we need to take on a case by case basis.

  • In ES6, nested named function declaration must be accepted and have the agreed ES6 semantics in strict code. As advised at conventions:no_non_standard_strict_decls, all major browsers currently reject nested named function declaration in strict code, so accepting them with the agreed semantics in ES6-strict is fully upwards compatible.ES6 remains as silent as ES5 about whether nested named function declarations can appear in non-strict code or what their semantics is there.

# Allen Wirfs-Brock (12 years ago)

On Jan 5, 2012, at 10:20 PM, Mark S. Miller wrote:

On Thu, Jan 5, 2012 at 10:13 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

On Jan 5, 2012, at 8:24 PM, Mark S. Miller wrote:

Hi Allen, if ES5 had only one mode, I'd understand this. But I thought we were trying to arrive at two modes, ES6 non-strict, to be backwards compat with ES5-non-strict, and ES6 strict, to be backwards compat with ES5-strict. I am perfectly happy to call ES6 non-strict "ES3", since ES5-non-strict really exists to be an ES3 compatibility mode, and was constrained to be backwards compatible from ES3. Likewise, I am happy to call the new ES6 strict simply "ES6".

When I talked about "ES5" in my original post I meant full ES5 including both strict and non-strict modes. Since ES6 is supposed to be a strict super set of ES5 strict mode, anything in the ES5-only category must be an exclusively non-strict mode feature (for example, with). I suppose you could call that category "ES3" but I decided to label it "ES5-only" to keep things focused on differences between ES5 and ES5.

Concretely, I am confused how your transition diagram is supposed to handle "use strict";. Reading your state machine literally, since "use strict"; is accepted by ES5, if it is accepted by ES6 (as I think we all agree it would be), then it would leave us in state ES5&ES6. Were you two base categories "ES6 non-strict" (or ES3) and "ES6 strict" (or ES6), then a "use strict"; would put us in your ES6 strict (or ES6) category, which is what I would have expected.

Yes, "use strict" is in the ES5&ES6 category. A ES5 completely strict mode program would start in State 5&6 and stay in that state for its entire "compilation". Only encountering use of a new ES6 feature would case a transition to State ES6. Looking at it another way, Both State ES5 and State 5&6 include support for both strict and non-strict ES5 code. State ES6 only allows strict code.

Them either I still don't understand, this proposal is broken, or we're talking about three modes. Take the following three programs:

My point wasn't to try to define modes. I think that is where we are miscommunicating.

There are exactly two language specifications we have to deal with. The ES5 spec. and the ES6 spec. A given ES source program may conform to one, or the other, or both (or neither). The exercise I was working through is how do you determine whether to process a given source file according to the ES5 spec. or the ES6 spec., without explicitly preassociating the source file with one or another. That is what my state machine does. It starts assuming the source file is in the intersection, where it it doesn't matter which specification you apply. As soon as it see a feature that is unique to one or the other of the specification it clamps to that specification for the entire program. The extra states are to deal with the situation where the appropriate specification to use for a feature can not be directly inferable from the syntax of the feature.

1) program using only ES3 features and no "use strict";

2) program using only ES5 strict features and saying "use strict";

3) program using ES6-only features.

Do these three programs operate in three different modes? If not, do #1 and #2 operate in the same mode, or do #2 and #3 operate in the same mode?

It isn't about "modes". #1 and #2 are ES5 programs and are processed as such (applying/not the appropriately strictness as per ES5) . #3 is an ES6 program is processed as such (including using the strict semantics that are universal to ES6).

# Mark S. Miller (12 years ago)

On Thu, Jan 5, 2012 at 11:47 PM, Allen Wirfs-Brock <allen at wirfs-brock.com>wrote:

1) program using only ES3 features and no "use strict";

2) program using only ES5 strict features and saying "use strict";

3) program using ES6-only features.

Do these three programs operate in three different modes? If not, do #1 and #2 operate in the same mode, or do #2 and #3 operate in the same mode?

It isn't about "modes". #1 and #2 are ES5 programs and are processed as such (applying/not the appropriately strictness as per ES5) . #3 is an ES6 program is processed as such (including using the strict semantics that are universal to ES6).

Ok, is there any observable difference between what you would have future browsers do, vs the equivalent mechanisms except that program #2 is categorized as an ES6 program and processed as such?

If there is no observable difference, good. Then it's only a matter of how we describe an agreed semantics. If there is an observable difference, how is this not three modes?

# Allen Wirfs-Brock (12 years ago)

On Jan 5, 2012, at 10:38 PM, Luke Hoban wrote:

...

Why ‘implicit explicit opt-in’ doesn’t seem reasonable

The prevalent alternatives presented in this thread are variations of “implicit explicit opt-in”, where use of some new syntax causes some part of the code inside or outside of it to start behaving differently (breaking changes). I think in practice this will be very confusing. Take this:

var x = typeof null; module { var y = typeof null; x == y // false! }

Note that my most resent postings were suggesting a different form of "implicit explicit opt-in": use of new syntax causes all of the code in the same source file to potentially behave differently

var x = typeof null; module { var y = typeof null; x == y // ----->true! }

In practice, I agree that we don't want to make such a breaking change for typeof. But this approach would allow to make "strict mode semantics" be implicit for any source file that uses any new ES6 syntactic features.

# Allen Wirfs-Brock (12 years ago)

On Jan 6, 2012, at 12:00 AM, Mark S. Miller wrote:

On Thu, Jan 5, 2012 at 11:47 PM, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

1) program using only ES3 features and no "use strict";

2) program using only ES5 strict features and saying "use strict";

3) program using ES6-only features.

Do these three programs operate in three different modes? If not, do #1 and #2 operate in the same mode, or do #2 and #3 operate in the same mode?

It isn't about "modes". #1 and #2 are ES5 programs and are processed as such (applying/not the appropriately strictness as per ES5) . #3 is an ES6 program is processed as such (including using the strict semantics that are universal to ES6).

Ok, is there any observable difference between what you would have future browsers do, vs the equivalent mechanisms except that program #2 is categorized as an ES6 program and processed as such?

If there is no observable difference, good. Then it's only a matter of how we describe an agreed semantics. If there is an observable difference, how is this not three modes?

There should be no observable difference. But the issue isn't how we described the (program) semantics. It is how we decide which semantics to apply.

The tricky cases are thing like:

function f(a) { arguments[0]=2; return a } print(f(1)); //2 if ES5, 1 if ES6

There is nothing in the source file that implies which specification to apply so for backwards computability a browser must default to interpreting such program as a ES5 program. Anything syntactically unique to ES5 (eg, use of a with statment) or ES6 (eg, use rest or spread) would force one interpretation or another

# Mark S. Miller (12 years ago)

On Fri, Jan 6, 2012 at 12:24 AM, Allen Wirfs-Brock <allen at wirfs-brock.com>wrote:

There should be no observable difference. But the issue isn't how we described the (program) semantics. It is how we decide which semantics to apply.

Got it. It still gives the web only two modes, but has the huge benefit that the ES6 spec can avoid describing the semantics of non-strict code. Cool.

# Axel Rauschmayer (12 years ago)

Ok, is there any observable difference between what you would have future browsers do, vs the equivalent mechanisms except that program #2 is categorized as an ES6 program and processed as such?

If there is no observable difference, good. Then it's only a matter of how we describe an agreed semantics. If there is an observable difference, how is this not three modes?

There should be no observable difference. But the issue isn't how we described the (program) semantics. It is how we decide which semantics to apply.

The tricky cases are thing like:

function f(a) { arguments[0]=2; return a } print(f(1)); //2 if ES5, 1 if ES6

There is nothing in the source file that implies which specification to apply so for backwards computability a browser must default to interpreting such program as a ES5 program. Anything syntactically unique to ES5 (eg, use of a with statment) or ES6 (eg, use rest or spread) would force one interpretation or another

But what you are saying is that ES6 is not a superset of ES5.strict, right?

I thought that there were only 2 semantics:

  1. ES5.non-strict (which is a superset of all previous versions) [a.k.a. "non-strict"]
  2. ES6 (which is a superset of ES5.strict) [a.k.a. "strict"]

My understanding is that #1 would be the default and when one encounters anything ES6-specific (an “ES6 trigger”) or "use strict" then the semantics switches to #2. => encountering a with statement would be fine, because #1 is the default, anyway. => encountering a with statement and either an ES6 trigger or a "use strict" would be an error.

As a human, I would want an ES6 trigger to appear as early as possible. I wouldn’t want to read through a file, encounter an ES6 trigger at the end and then have to revise the understanding of the code that I had so far. I like the whole-file-or-nothing approach for switching semantics that you proposed.

# Herby Vojčík (12 years ago)

-----Pôvodná správa---

# Mark S. Miller (12 years ago)

No sorry, I just spotted the flaw. The observable difference is that a conforming browser is not required by the (ES5 + ES6) specs to provide any non-triggering ES6 features for program #2. In that case, we again have three mode.

For example, since legacy constrains us from making nested named function declarations a triggering feature, if program #2 has a nested named function and the browser rejected it, that browser would still conform to both the ES5 and ES6 spec.

The easy fix is to make "use strict"; a triggering condition. For non-strict code, by the state machine, the ES6 spec would still delegate to the ES5 spec. And the ES6 spec would otherwise be the same. But the strict portion of the ES5 spec would simply be dead code, because all of the conditions that would trigger it have already triggered the state machine into using the ES5 spec.

# Allen Wirfs-Brock (12 years ago)

On Jan 6, 2012, at 8:03 AM, Mark S. Miller wrote:

No sorry, I just spotted the flaw. The observable difference is that a conforming browser is not required by the (ES5 + ES6) specs to provide any non-triggering ES6 features for program #2. In that case, we again have three mode.

For example, since legacy constrains us from making nested named function declarations a triggering feature, if program #2 has a nested named function and the browser rejected it, that browser would still conform to both the ES5 and ES6 spec.

Implementations that currently support extensions to ES5 (and wish to continue to support them) must classify their extensions into one of the four categories I identified and then process them according to the state machine. Because no currently implementation of function declarations within blocks (that I'm aware of) matches the ES6 lexical scoping semantics, it would expect such function declarations to be classified as ES5~ES6. Then, according to the state machine, a program like:

function f(g) { //not the following will produce inconsistent results among common browsers if (!g) { function g() {return 1} } else if (typeof g !== 'function') { function g() {return 2} } return g; }

will be processed using the (implementation extended) ES5 specification and both f and g would presumably be non-strict functions. If you wanted the above to be processed as ES6 code you would need to add some ES6-only features such as: let ES6; or use some other forced opt-in such as a version in the MIME type.

The above is exactly analogies to how any "standard" ES5~ES6 features would be treated.

# Mark S. Miller (12 years ago)

AFAICT, this agrees with my analysis of what your proposal means. How does this not result in three modes?

# Brendan Eich (12 years ago)

(This grew out of a conversation Allen and I had yesterday -- great to see it developed.)

One thing to make clear:

ES5~EAS6: The construct has identical syntax and static semantics in both ES5 and ES6, but differing semantics. For example, accessing a formal parameter after an assignment to the corresponding element of the function's arguments object.

This is something we propose to do with completion reform, and also until this year for typeof null.

It's clear we can't get away with changing typeof null == "null". Even with full opt-in, it's a runtime migration hazard (one of the five fingers of fate). I withdraw it -- I'm the one who proposed it in lieu of an Object.isObject predicate -- but I do not think we should add Object.isObject either.

Rather, we need to rethink reflection on types in light of not only null vs. object, but value types/proxies. I'd rather not rush that. In the mean time, and for lo these 16 years (heading toward 17!), developers have coped and can continue to do so with typeof x == "object" && x === null or simpler (!x, x == null, other context-specialized combinations).

In general, the latest "new syntax is its own opt-in" thinking, with Allen's state machine approach, means our five fingers of fate have to be small enough that we can get away with them. At least Mark and I believe completion reform (making the completion value depend on a statically decidable expression-statement) is the only such finger we can get away with folding right now.

If you want to explicitly force ES6 processing put a: let ES6;

Or (no quotes)

use strict;

I think we want this pragma supported, not only the string-literal expression-statement "directive".

at the top of the source file.

If you want to explicitly force ES5 processing put a: with (5); at the top of the source file

That will potentially deoptimize the top level for some engines, but maybe it doesn't matter. I don't expect it to catch on ;-).

# Brendan Eich (12 years ago)

On Jan 6, 2012, at 11:23 AM, Mark S. Miller wrote:

AFAICT, this agrees with my analysis of what your proposal means. How does this not result in three modes?

Counting modes is not productive, is it? All major implementations have extended ES5. It's likely extensions will continue to precede standardization. Do these make ongoing new modes?

Rather, we should minimize the state machine and how we talk about it. We could generalize it using Curr, Next, Curr&Next, and Curr-Next labels.

# Allen Wirfs-Brock (12 years ago)

On Jan 6, 2012, at 11:23 AM, Mark S. Miller wrote:

AFAICT, this agrees with my analysis of what your proposal means. How does this not result in three modes?

I guess I don't understand exactly what you mean by a "mode" or why the number of modes is particularly interesting.

From an implementation perspective, I suppose you consider each state in my FSM a "mode" in which case we have 4 processing modes. Or

If you are using "mode" to classify the semantics of arguments and assignment to undeclared identifiers then there are two modes: "strict" and "non-strict"

If you are using "mode" to classify what can occur in a StatementList there are three modes: ES5, ES5-strict, ES-6.

# Axel Rauschmayer (12 years ago)

Rather, we should minimize the state machine and how we talk about it. We could generalize it using Curr, Next, Curr&Next, and Curr-Next labels.

I’m awfully sorry for belaboring this point. But the labels and the quote below don’t go together.

Quoting Brendan:

  • ES6 is a superset of ES5.strict.

That's always been promised.

Then I would only expect two labels: ES6 and non-strict

ES6-only => (a subset of) ES6

ES5-only => only possible for non-strict constructs => non-strict

ES5&ES6 => (a subset of) ES6

ES5~EAS6 => not possible (“The construct has identical syntax and static semantics in both ES5 and ES6, but differing semantics.”)

# Allen Wirfs-Brock (12 years ago)

On Jan 6, 2012, at 11:52 AM, Brendan Eich wrote:

(This grew out of a conversation Allen and I had yesterday -- great to see it developed.)

One thing to make clear:

ES5~EAS6: The construct has identical syntax and static semantics in both ES5 and ES6, but differing semantics. For example, accessing a formal parameter after an assignment to the corresponding element of the function's arguments object.

This is something we propose to do with completion reform, and also until this year for typeof null.

It's clear we can't get away with changing typeof null == "null". Even with full opt-in, it's a runtime migration hazard (one of the five fingers of fate). I withdraw it -- I'm the one who proposed it in lieu of an Object.isObject predicate -- but I do not think we should add Object.isObject either.

Rather, we need to rethink reflection on types in light of not only null vs. object, but value types/proxies. I'd rather not rush that. In the mean time, and for lo these 16 years (heading toward 17!), developers have coped and can continue to do so with typeof x == "object" && x === null or simpler (!x, x == null, other context-specialized combinations).

In general, the latest "new syntax is its own opt-in" thinking, with Allen's state machine approach, means our five fingers of fate have to be small enough that we can get away with them. At least Mark and I believe completion reform (making the completion value depend on a statically decidable expression-statement) is the only such finger we can get away with folding right now.

If you want to explicitly force ES6 processing put a: let ES6;

Or (no quotes)

use strict;

I think we want this pragma supported, not only the string-literal expression-statement "directive".

If we end up with all of ES6 being a super set of ES5 strict, then I don't see a lot value in saying: use strict;

I would think that: use ES6; //or use version 6; etc would better express the user intent.

BTW, I would interpret this as meaning "at least ES6" and still do feature driven version detection of future versions using an expanded state machine.

at the top of the source file.

If you want to explicitly force ES5 processing put a: with (5); at the top of the source file

That will potentially deoptimize the top level for some engines, but maybe it doesn't matter. I don't expect it to catch on ;-).

Other ES5 opt-ins at the top level include:

var arguments;

function ES5(yes,yes){};

These probably won't cause any deoptimization.

However, I agree that it would be rare for someone to actually need to do this. The intent was more to emphasize that the way you force a particular spec-level interpretation is to code something that is unique to that spec-level.

# Brendan Eich (12 years ago)

On Jan 6, 2012, at 12:09 PM, Axel Rauschmayer wrote:

Rather, we should minimize the state machine and how we talk about it. We could generalize it using Curr, Next, Curr&Next, and Curr-Next labels.

I’m awfully sorry for belaboring this point. But the labels and the quote below don’t go together.

Quoting Brendan:

  • ES6 is a superset of ES5.strict.

That's always been promised.

Then I would only expect two labels: ES6 and non-strict

You're counting different beans from Mark's "modes" and from Allen's states.

The reason the state machine matters is implementation (including the fine spec, the normative implementation). Authors can think of writing non-strict ES5 or lower, or ES5 strict -- or ES6 if they use a bit of novelty. Different beans again.

I'm not sure what informs your label count expectation. In writing JS for the web over the next several years, you might have to worry quite a bit about ES5 strict vs. ES6. You can't just assume ES6 works everywhere that ES5 strict works.

# Brendan Eich (12 years ago)

On Jan 6, 2012, at 12:25 PM, Allen Wirfs-Brock wrote:

On Jan 6, 2012, at 11:52 AM, Brendan Eich wrote:

Or (no quotes)

use strict;

I think we want this pragma supported, not only the string-literal expression-statement "directive".

If we end up with all of ES6 being a super set of ES5 strict, then I don't see a lot value in saying: use strict;

The value is that the string literal is non-breaking in pre-ES5 implementations, which has been a source of real bugs (concatenation, too-early adoption).

Explicit opt in via

use strict;

seems better to me than anything with a version number in it. More below.

I would think that: use ES6; //or use version 6; etc would better express the user intent.

BTW, I would interpret this as meaning "at least ES6" and still do feature driven version detection of future versions using an expanded state machine.

While the ECMA editions will be numbered (unpredictably!), and RFC4329 points toward post-hoc standardization of the ;version= MIME type parameter, I do not think we should embed version numbers in pragmas.

We could, certainly. That was the plan of record until recently. It matches the ;version= progression we're likely to want anyway, to hide new code from being loaded by old browsers with inevitable syntax errors whose reporitng slows the futile loads even further.

But I'd rather leave ;version= to the MIME type parameter, which is just a post-hoc reflection of ECMA's edition numbering. This reduces the tendency to think of versions, modes, and the like. It's a human factors thing. Versions suck, Hixie and Anne and others were right to push back on them, even if they didn't solve the forward compatibility problem (no one has) or are in denial about HTML5 (with 5) :-P.

Other ES5 opt-ins at the top level include:

var arguments;

function ES5(yes,yes){};

These probably won't cause any deoptimization.

However, I agree that it would be rare for someone to actually need to do this. The intent was more to emphasize that the way you force a particular spec-level interpretation is to code something that is unique to that spec-level.

Sure.

# Axel Rauschmayer (12 years ago)

Then I would only expect two labels: ES6 and non-strict

You're counting different beans from Mark's "modes" and from Allen's states.

The reason the state machine matters is implementation (including the fine spec, the normative implementation). Authors can think of writing non-strict ES5 or lower, or ES5 strict -- or ES6 if they use a bit of novelty. Different beans again.

Ah, got it! You want ECMA-262 version 6 to allow an à la carte approach: implementors can choose between non-strict ES5, strict ES5, ES6, etc.

I'm not sure what informs your label count expectation. In writing JS for the web over the next several years, you might have to worry quite a bit about ES5 strict vs. ES6. You can't just assume ES6 works everywhere that ES5 strict works.

I was thinking about how to specify only (exclusively) an ES6 environment. You pretend to live in a “perfect ES6 world” and then only have two labels. There are two ways out of this world:

  • Non-ES6 environments for implementors: refer to ECMA-262 version 5.1.

  • Non-ES6 environments for developers: simulate ES6 (via static compilation, dynamic compilation, etc.).

# Brendan Eich (12 years ago)

On Jan 6, 2012, at 1:03 PM, Axel Rauschmayer wrote:

Then I would only expect two labels: ES6 and non-strict

You're counting different beans from Mark's "modes" and from Allen's states.

The reason the state machine matters is implementation (including the fine spec, the normative implementation). Authors can think of writing non-strict ES5 or lower, or ES5 strict -- or ES6 if they use a bit of novelty. Different beans again.

Ah, got it! You want ECMA-262 version 6 to allow an à la carte approach: implementors can choose between non-strict ES5, strict ES5, ES6, etc.

No! ES6 will be a normative all or none spec, ignoring informative annexes and Annex B (revised to be normative-optional, meaning if you implement these APIs you must follow this annex, but you are not required to implement them).

The issue is how the spec and implementations decide what is supported, and when to raise an error on new syntax mixed (after) old non-strict code (e.g., 'with').

I'm not sure what informs your label count expectation. In writing JS for the web over the next several years, you might have to worry quite a bit about ES5 strict vs. ES6. You can't just assume ES6 works everywhere that ES5 strict works.

I was thinking about how to specify only (exclusively) an ES6 environment. You pretend to live in a “perfect ES6 world” and then only have two labels. There are two ways out of this world:

  • Non-ES6 environments for implementors: refer to ECMA-262 version 5.1.

  • Non-ES6 environments for developers: simulate ES6 (via static compilation, dynamic compilation, etc.).

That's fair, and some developers will just assume ES6 and require latest l33t browsers. There are many possible bean-counting approaches depending on your business, hobby, mood, etc.

For the spec we need an algorithm for syntax-as-opt-in. That's what the state machine is about.

# Axel Rauschmayer (12 years ago)

The issue is how the spec and implementations decide what is supported, and when to raise an error on new syntax mixed (after) old non-strict code (e.g., 'with').

Ah, OK. I thought that one would be able to lump together ES5.non-strict and all prior ES versions on one hand and ES6 and ES5.strict on the other hand. But it makes sense that even if developers (engine users) are presented with something simple, implementors have to take care of many more details.

# Brendan Eich (12 years ago)

On Jan 6, 2012, at 1:35 PM, Axel Rauschmayer wrote:

The issue is how the spec and implementations decide what is supported, and when to raise an error on new syntax mixed (after) old non-strict code (e.g., 'with').

Ah, OK. I thought that one would be able to lump together ES5.non-strict and all prior ES versions on one hand and ES6 and ES5.strict on the other hand.

Notice that two of the four states contain possible outcome

terminate with Error: invalid combination of ES5 and ES6 features

These are the states labeled ES5 and ES6.

It should be clear that you need more than two states to judge whether a given hunk of code is ES5 non-strict (or lower) -- the default unversioned <script> JS we know today -- or ES5-strict or higher. To have only two states in the machine, you would need version-based opt-in.

But don't worry about counting states. Count minimal or even non-minimal "modes" if you like ;-).

But it makes sense that even if developers (engine users) are presented with something simple, implementors have to take care of many more details.

A point that I flog often. The few and skilled implementors should take some complexity on behalf of the whole user base, provided that complexity in implementation and specification saves the bulk of users enough by simplifying migration and adoption.

IMHO we are onto something good here. The complexity for the implementors does not too bad (I'm looking at it right now for SpiderMonkey), and the user-facing wins are huge.

# Mark S. Miller (12 years ago)

Axel, thanks. This is the critical point, so no apologies needed for belaboring. Allen, what I mean is exactly what Axel says here.

Look at it another way. Right now we have two normative modes: ES5 strict and ES5 non-strict. State machine aside, ES6 introduces a new single mode normative spec. If the state machine may delegate to any of these three normative specs we have three modes. If the state machine may only delegate to ES5-non-strict or ES6, i.e., if the ES5-strict spec becomes dead code as of conformance with state machine + ES6 spec, then we have two modes.

To get this effect, we need only classify ES5's "use strict"; directive as ES6-only. If any objection to doing so has been stated, I missed it. Is there a reason not to do so? It's a one line change that leaves the rest of your proposal unperturbed and solves this problem.

# Mark S. Miller (12 years ago)

On Fri, Jan 6, 2012 at 12:25 PM, Allen Wirfs-Brock <allen at wirfs-brock.com>wrote: [...]

Other ES5 opt-ins at the top level include:

var arguments;

function ES5(yes,yes){};

Please stop calling these ES5 opt-ins. They preclude the Program from being strict, and so are forcing ES5-not-strict. Non confusing names for this are:

ES5-non-strict Non-strict ES3

By continuing to call this simply ES5 while precluding the program from being an ES5-strict program, you confuse the crucial issue I'm trying to clarify.

# Mark S. Miller (12 years ago)

On Fri, Jan 6, 2012 at 12:29 PM, Brendan Eich <brendan at mozilla.com> wrote:

On Jan 6, 2012, at 12:09 PM, Axel Rauschmayer wrote:

Rather, we should minimize the state machine and how we talk about it. We could generalize it using Curr, Next, Curr&Next, and Curr-Next labels.

I’m awfully sorry for belaboring this point. But the labels and the quote below don’t go together.

Quoting Brendan:

  • ES6 is a superset of ES5.strict.

That's always been promised.

Then I would only expect two labels: ES6 and non-strict

You're counting different beans from Mark's "modes" and from Allen's states.

Hi Brendan, as I read it, Axel captures exactly the two modes I have in mind.

The reason the state machine matters is implementation (including the fine spec, the normative implementation). Authors can think of writing non-strict ES5 or lower, or ES5 strict -- or ES6 if they use a bit of novelty. Different beans again.

I'm not sure what informs your label count expectation. In writing JS for the web over the next several years, you might have to worry quite a bit about ES5 strict vs. ES6. You can't just assume ES6 works everywhere that ES5 strict works.

The issue is: What does it mean for a browser to be standards compliant once it is fully conformant with ES6? Yes, of course there will be a long phase of partial ES6 compliance as features are incrementally rolled out. Just as there was with ES5. But during this period, no one claims full conformance with ES6, so standards compliance mean only compliance with ES5. To be standards compliant once one is ES6 compliant, the state machine

  • ES6 keeps some portion of the ES5 spec as a live normative spec because it is reachable from the state machine. The only question is: Which portion is reachable? With Allen's plan, all of it. With the one line revision Axel and I have in mind, the ES5-strict spec stops being reachable as normative for ES6 compliant browsers. It is dead code that can be considered garbage.

In the ES6 era, I hope to be able to say "ES5-strict is dead. Long live ES6!".

However, ES5-non-strict (or "non-strict", or "ES3") will continue to live for the foreseeable future. It will probably outlive most of us.

# Herby Vojčík (12 years ago)

I think Allen had another point. What you are counting, are semantic modes of the code, that is, runtime. What Allen counts, are specification modes of the language, that is, compilation. These are different beans, as he said earlier.

Things that Allen defines (afaict) are:

  • compilation targets

ES5 ::= "When run, the program will be interpreted according to ES5 spec. In particular, parts with "use strict"; will be interpreted with ES5 strict mode semantics and parts without it with ES5 non-strict mode semantics. In other words, it will be compiled according to ES5 spec."

ES6 ::= "When run, the program will be interpreted according to ES6 spec. In particular, parts with "use strict"; will be interpreted with ES6 (strict) mode semantics and part without it also with ES6 (strict) mode semantics. In other words, it will be compiled according to ES5 spec."

  • categories of the code elements with respect to compilation targets (they are event in the pre-compilation parsing state machine)

Identical-in-both (ES5&ES6), Valid-but-differs (ES5~ES6), Valid-only-in-ES5 (ES5-only), Valid-only-in-ES6 (ES6-only)

  • states and transitions of the pre-compilation parsing state machine

5&6, ES5, ES6, Compat5 states transitions for all of them with respect to events above (that is, categories)

The output of the state machine is the compilation target (inferred directly from the state at which the state machine finishes: ES5, ES5, ES6 and ES5, respectively), eventually an early error that prevents compilation because of conflicting ES5-only and ES6-only code was detected while parsing.

"use strict"; and its two modes are only important here, when the program is being compiled (when in state machine, it should imo trigger "Identical-in-both" event).

He also recommends opt-ins to be sure to get ES5 target or ES6 target (unless there is conflict in code, of course) as the output of the state machine by citing pieces of code with no functionality but triggering "Valid-only-in-ES5" or "Valid-only-in-ES6" events in the parsing state machine, thereby fixing its state to ES5 or ES6.

Herby

-----Pôvodná správa---

# Claus Reinke (12 years ago)
if current construct is in ES6-only, abort current compilation and 

restart from beginning, starting in State ES6

Basically using any ES6 features makes it an ES6 program. Using any ES5-only feature makes it an ES5 program. Combining ES5-only and ES6 features results in an invalid program. If a Program can not be explicitly identified as either ES5 or ES6, it is treated as an ES5 program.

Could you please clarify for me: if I get a single-file codebase, say jquery's 6k or so lines, will I have to scan the whole file for ES6-only features before I can even tell whether to apply ES6 or ES5 semantics? And will this problem increase by one level with every future version of ES?

ES5 already has the var-"hoisting" oddity where I might have to scan the whole file before being able to decide whether a given variable occurrence is bound to a given variable declaration. I'm not alone in not wanting to see (another) such hazard (*).

Btw, I'm really uncomfortable with implicit feature tracking - it works ok for people who follow all the relevant mailinglist discussions and spec versions, but it leaves in the dark all those who "just code in JS" and enter a situation where their code base might have to be interpreted according to any of a number of specs (most JS coders do not even read one version of the spec, but rely on blogs and books, which will equally show no explicit in-source sign of what spec version their examples refer to and whether they are out of date).

Making feature-based versioning explicit (by pragma opt-in) doesn't solve all problems, but at least everybody knows what everyone else is talking about, and help can be offered ("if you want ES6 semantics for let and yield, write 'use let,yield' or just 'use ES6', if you want JS1.8 semantics for these features, use ..; if you want to concatenate old and new code, protect the old code with 'use ES5' and the new code with 'use ES6'").

By making feature-based versioning implicit (by feature use), only experts will be able to say "wait, I remember that kind of issue - perhaps your engine has switched to another ES version. could you check whether your source has any yields in it, or perhaps destructuring, or perhaps a with, or perhaps a vat? yes, please check even seemingly unrelated parts of the source.".

Also, what happened to the bold "this version will break a few things, but we'll end up with a much cleaner language spec"?

I'm not saying you are wrong - if you can pull it off, great! But the new plan reminds me strongly of bad experiences in Haskell-land.

Claus

(*) At least one popular tool (jslint) has decided not to implement the resulting 2-pass complexity (find var declarations, then parse for real) but restricts the input language instead, and makes do with a single pass over the source and naïve scope-tracking.

If I remember correctly, simply lifting jslint's unpopular vars-first restriction would lead to incorrect internal scope tracking (I don't know how jshint handles this, btw?).

# Gavin Barraclough (12 years ago)

On Jan 7, 2012, at 2:22 PM, Claus Reinke wrote:

Could you please clarify for me: if I get a single-file codebase, say jquery's 6k or so lines, will I have to scan the whole file for ES6-only features before I can even tell whether to apply ES6 or ES5 semantics? And will this problem increase by one level with every future version of ES?

Btw, I'm really uncomfortable with implicit feature tracking - it works ok for people who follow all the relevant mailinglist discussions and spec versions, but it leaves in the dark all those who "just code in JS" and enter a situation where their code base might have to be interpreted according to any of a number of specs (most JS coders do not even read one version of the spec, but rely on blogs and books, which will equally show no explicit in-source sign of what spec version their examples refer to and whether they are out of date).

I share these concerns.

Would the implicit opt-in proposal not mean that any programmer wanting to pick up JavaScript would need to learn the history of when different syntactic constructs came into the language specification in order to understand the semantics that a given script would be evaluated with?

G.

# Brendan Eich (12 years ago)

On Jan 7, 2012, at 8:00 PM, Gavin Barraclough wrote:

Would the implicit opt-in proposal not mean that any programmer wanting to pick up JavaScript would need to learn the history of when different syntactic constructs came into the language specification in order to understand the semantics that a given script would be evaluated with?

If there's new syntax somewhere, the consumer of the code may have to read and understand it. But let's be real: JQuery users do not read and understand that library's every line. They use its well-documented APIs.

Remember, we are not proposing breaking semantic shifts of meaning for existing syntax. So the realistic worry is that you have code with arguments[i] aliasing a formal, and this is required for correct operation, and you then start using ES6 features (which imply ES5-strict), which breaks arguments aliasing.

# Axel Rauschmayer (12 years ago)

On Jan 7, 2012, at 23:22 , Claus Reinke wrote:

But the new plan reminds me strongly of bad experiences in Haskell-land.

That sounds interesting. Is this documented somewhere?

# Gavin Barraclough (12 years ago)

On Jan 7, 2012, at 8:39 PM, Brendan Eich wrote:

Remember, we are not proposing breaking semantic shifts of meaning for existing syntax. So the realistic worry is that you have code with arguments[i] aliasing a formal, and this is required for correct operation, and you then start using ES6 features (which imply ES5-strict), which breaks arguments aliasing.

Hmmm, I was thinking this proposal implied much more of a breaking change (e.g. removing the global object from scope), but if the change in semantics largely comes down to enabling ES5-strict then maybe this isn't so bad. If we are talking about implicitly enabling ES5-strict for the whole program, I would think there may be a quite a few more hazards to consider? (code that implicitly introduces variables onto the global object, assumes this conversion to global object value in function calls, uses callee/caller/arguments, etc).

Still, I'm warming up to the idea considerably if the implicit opt-in is triggering no major breaking changes.

G.

# Brendan Eich (12 years ago)

On Jan 6, 2012, at 7:48 PM, Mark S. Miller wrote:

Then I would only expect two labels: ES6 and non-strict

You're counting different beans from Mark's "modes" and from Allen's states.

Hi Brendan, as I read it, Axel captures exactly the two modes I have in mind.

Ok, good to have fewer positions :-).

The reason the state machine matters is implementation (including the fine spec, the normative implementation). Authors can think of writing non-strict ES5 or lower, or ES5 strict -- or ES6 if they use a bit of novelty. Different beans again.

I'm not sure what informs your label count expectation. In writing JS for the web over the next several years, you might have to worry quite a bit about ES5 strict vs. ES6. You can't just assume ES6 works everywhere that ES5 strict works.

The issue is: What does it mean for a browser to be standards compliant once it is fully conformant with ES6? Yes, of course there will be a long phase of partial ES6 compliance as features are incrementally rolled out. Just as there was with ES5.

Still is.

But during this period, no one claims full conformance with ES6, so standards compliance mean only compliance with ES5. To be standards compliant once one is ES6 compliant, the state machine + ES6 keeps some portion of the ES5 spec as a live normative spec because it is reachable from the state machine. The only question is: Which portion is reachable? With Allen's plan, all of it. With the one line revision Axel and I have in mind, the ES5-strict spec stops being reachable as normative for ES6 compliant browsers. It is dead code that can be considered garbage.

Your post two above the one to which I'm replying proposes to have only ES5 non-strict and ES6, with "use strict"; (the string literal directive) enabling ES6. I don't a difference except in state-machine labels with what Allen proposes.

Allen has 5&6, ES5, ES6, and Compat5. Relabel these to

ES5-nonstrict-ES6-intersection ES5-nonstrict ES6 ES5-nonstrict-differs-from-ES6

Since ES5-strict is a subset of ES6, it doesn't require new states.

Function declarations in blocks and (depending on implementation) const extensions fall into ES5-nonstrict-differs-from-ES6 as noted. That is, "ES5-nonstrict" must be read to include an implementations extensions that pre-date ES5, that are enabled without "use strict";, and of course which conflict with ES5 strict.

You previously wrote:

For example, since legacy constrains us from making nested named function declarations a triggering feature, if program #2 [one with "use strict"; and conforming to ES5 strict] has a nested named function and the browser rejected it, that browser would still conform to both the ES5 and ES6 spec.

ES6 will normatively require block-nested function declarations to be supported, with certain semantics. So I don't agree that a conforming ES6 implementation could reject such functions in blocks.

You continued:

The easy fix is to make "use strict"; a triggering condition.

I agree that this follows from the state label definitions (whatever their names) and the state machine.

Then you wrote:

For non-strict code, by the state machine, the ES6 spec would still delegate to the ES5 spec. And the ES6 spec would otherwise be the same. But the strict portion of the ES5 spec would simply be dead code, because all of the conditions that would trigger it have already triggered the state machine into using the ES5 spec.

Here again I'm confused. the ES6 spec is not going to delegate to a separate and older edition, namely ES5. It will be self-contained. So there must be something in the ES6 spec that defines how to process "use strict"; or new ES6 syntax, and how that makes duplicate formals an early error, etc. etc.

IOW ECMA-262 Ed. 6 must contain some kind of state machinery for specifying opt-in.

In the ES6 era, I hope to be able to say "ES5-strict is dead. Long live ES6!".

Ok, I can adjust labels to agree with this. But it doesn't relieve the ES6 spec from talking about opt-in from ES5-nonstrict, so are we really just arguing about names or labels? If so, great -- those are important to get right.

If I seemed to disagree on number of modes, it may be because I don't see how a conforming ES6 implementation could continue to reject extensions that ES5-strict rejects (e.g., functions in blocks). ES6 will require functions-in-blocks to work a certain way.

Any implementation that supported an extension under ES5-strict where the semantics for the same syntax differ between the extension and ES6 will have to suffer (SpiderMonkey let and const fall into this category). But that is SpiderMonkey's headache, not ECMA-262's.

However, ES5-non-strict (or "non-strict", or "ES3") will continue to live for the foreseeable future. It will probably outlive most of us.

Yup.

# Brendan Eich (12 years ago)

On Jan 7, 2012, at 11:05 PM, Brendan Eich wrote:

Allen has 5&6, ES5, ES6, and Compat5. Relabel these to

ES5-nonstrict-ES6-intersection ES5-nonstrict ES6 ES5-nonstrict-differs-from-ES6

Since ES5-strict is a subset of ES6, it doesn't require new states.

Again we wink at completion reform -- it changes semantics such that ES5-strict is not a subset of ES6 but we believe no code will notice (we could be wrong).

We give up on typeof null and removing the global object. The free variable analysis based on global properties still gives early error on typo wins. That makes the subset relation technically false too but let's also wink at it by considering only strict programs that reach runtime under either an ES5 or an ES6 implementation.

# Brendan Eich (12 years ago)

On Jan 7, 2012, at 9:35 PM, Gavin Barraclough wrote:

On Jan 7, 2012, at 8:39 PM, Brendan Eich wrote:

Remember, we are not proposing breaking semantic shifts of meaning for existing syntax. So the realistic worry is that you have code with arguments[i] aliasing a formal, and this is required for correct operation, and you then start using ES6 features (which imply ES5-strict), which breaks arguments aliasing.

Hmmm, I was thinking this proposal implied much more of a breaking change (e.g. removing the global object from scope),

Nope, as dherman's o.p. said: "giving up". But not the free variable analysis based on implicitly imported global object properties, for early errors on typos, of course.

but if the change in semantics largely comes down to enabling ES5-strict then maybe this isn't so bad. If we are talking about implicitly enabling ES5-strict for the whole program, I would think there may be a quite a few more hazards to consider? (code that implicitly introduces variables onto the global object, assumes this conversion to global object value in function calls, uses callee/caller/arguments, etc).

See Allen's state machine. It requires state transitions for the syntax whose meaning shifted from ES5 (non-strict, as Mark points out this is only part of ES5 so a misnamed label) to ES6.

Still, I'm warming up to the idea considerably if the implicit opt-in is triggering no major breaking changes.

The state machine won't trigger breaking runtime shifts but it will make early errors out of inconsistent combinations of ES5-nonstrict and ES6 features in a single program.

# Axel Rauschmayer (12 years ago)

On Jan 8, 2012, at 8:05 , Brendan Eich wrote:

Allen has 5&6, ES5, ES6, and Compat5. Relabel these to

ES5-nonstrict-ES6-intersection ES5-nonstrict ES6 ES5-nonstrict-differs-from-ES6

Since ES5-strict is a subset of ES6, it doesn't require new states.

I like these labels!

Isn’t ES5.nonstrict the union of ES5.nonstrict-ES6-intersection and ES5.nonstrict-differs-from-ES6? If yes then ES5.nonstrict disappears and we might have a venn diagram intersecting ES5.nonstrict and ES6: ES5.nonstrict-only (=ES5.nonstrict-differs-from-ES6) ES5.nonstrict-ES6-intersection ES6-only (=ES6-differs-from-ES5.nonstrict)

This might be about to the question as to whether there should be a mode that combines ES6-differs-from-ES5.strict constructs with ES5.nonstrict. I don’t think there should be.

# Mark S. Miller (12 years ago)

On Sat, Jan 7, 2012 at 11:05 PM, Brendan Eich <brendan at mozilla.com> wrote: [...]

so are we really just arguing about names or labels? If so, great -- those are important to get right.

[...]

While I agree it's important to get labels right, if that is indeed the only remaining issue here, that's wonderful. I'm mellow about labels others find attractive, as long as we agree on observables.

I think there is an observable difference between what I'm advocating and what Allen is. Not as sure about your position. But it is possible I'm misunderstanding something and there is no observable difference. At this point, we've narrowed the issue enough that we should probably postpone this final step till we can engage verbally with lower latency -- at the upcoming meeting.

# Brendan Eich (12 years ago)

On Jan 8, 2012, at 8:10 AM, Mark S. Miller wrote:

On Sat, Jan 7, 2012 at 11:05 PM, Brendan Eich <brendan at mozilla.com> wrote: [...] so are we really just arguing about names or labels? If so, great -- those are important to get right. [...]

While I agree it's important to get labels right, if that is indeed the only remaining issue here, that's wonderful. I'm mellow about labels others find attractive, as long as we agree on observables.

I think there is an observable difference between what I'm advocating and what Allen is. Not as sure about your position.

I think the only open issue is whether

"use strict";

opts into completion reform and any other semantic change that does not have a syntactic trigger.

I'm ok with trying completion reform as an extension to "use strict"; in ES5+ implementations, ASAP -- more than ok, really. It would help prove completion reform is non-breaking.

# Mark S. Miller (12 years ago)

On Sun, Jan 8, 2012 at 8:15 AM, Brendan Eich <brendan at mozilla.com> wrote:

On Jan 8, 2012, at 8:10 AM, Mark S. Miller wrote:

On Sat, Jan 7, 2012 at 11:05 PM, Brendan Eich <brendan at mozilla.com> wrote: [...]

so are we really just arguing about names or labels? If so, great -- those are important to get right.

[...]

While I agree it's important to get labels right, if that is indeed the only remaining issue here, that's wonderful. I'm mellow about labels others find attractive, as long as we agree on observables.

I think there is an observable difference between what I'm advocating and what Allen is. Not as sure about your position.

I think the only open issue is whether

"use strict";

opts into completion reform and any other semantic change that does not have a syntactic trigger.

I'm ok with trying completion reform as an extension to "use strict"; in ES5+ implementations, ASAP -- more than ok, really. It would help prove completion reform is non-breaking.

That is an example of the kind of issue I am concerned about -- and it is much more than a terminology difference. Any other cleanups we do in ES6 --- that we believe to be practically backward compatible with ES5-strict practice but not with the normative spec nor with test262 --- would fall into the same category as completion reform. So the status of things like we imagine completion reform to be, whether or not completion reform itself is one of those, is something we need to resolve.

The other change I hope fits into the same bucket is < strawman:fixing_override_mistake>.

Right now, because of pressure from test262, we are in danger of having all browsers conform to this mistake, at which point it may be too late to fix it. Today, the diversity of actual browser behaviors means it is still possible to fix this mistake, much as the diversity of ways ES3 implementations were broken made it possible for ES5 to fix many mistakes.

And there is one further issue I think is worth clearing up in email here.

What do we expect to be the normative status of the state machine itself and of the ES5 spec, after ES6 becomes official, for a browser that claims to be standards compliant, including ES6 compliant? AFAICT, no one has commented on this specifically, and we may be reading different assumptions into Allen's proposal.

What I am assuming Allen's proposal means is that the state machine would also be normative, and therefore those parts of the ES5 spec that are reachable from this state machine would remain normative as well. As I understand ECMA rules, it would be at least unusual for the earlier edition of the spec to remain normative even for systems claiming conformance to the later edition of the "same" spec. (same by spec number, i.e., 262).

# Brendan Eich (12 years ago)

On Jan 7, 2012, at 11:41 PM, Axel Rauschmayer wrote:

Isn’t ES5.nonstrict the union of ES5.nonstrict-ES6-intersection and ES5.nonstrict-differs-from-ES6? If yes then ES5.nonstrict disappears and we might have a venn diagram intersecting ES5.nonstrict and ES6:

ES5.nonstrict-only  (=ES5.nonstrict-differs-from-ES6)
ES5.nonstrict-ES6-intersection
ES6-only  (=ES6-differs-from-ES5.nonstrict)

As Mark just wrote this depends on how we resolve some fine points (completion reform among them).

This might be about to the question as to whether there should be a mode that combines ES6-differs-from-ES5.strict constructs with ES5.nonstrict. I don’t think there should be.

Oh no, that's right out. We can't have 'let' be reserved conditionally without more complexity (we could just reserve it and see what breaks, but then that's not ES5-nonstrict is it :-P).

# Brendan Eich (12 years ago)

On Jan 8, 2012, at 8:28 AM, Mark S. Miller wrote:

I'm ok with trying completion reform as an extension to "use strict"; in ES5+ implementations, ASAP -- more than ok, really. It would help prove completion reform is non-breaking.

That is an example of the kind of issue I am concerned about -- and it is much more than a terminology difference. Any other cleanups we do in ES6 --- that we believe to be practically backward compatible with ES5-strict practice but not with the normative spec nor with test262 --- would fall into the same category as completion reform. So the status of things like we imagine completion reform to be, whether or not completion reform itself is one of those, is something we need to resolve.

Yes. This is not easy to resolve by a-prior reasoning, though. We might just do nothing incompatible, but we could also probably get away with completion reform and your fixing-override-mistake change. Some experimentation in nightly and even longer-lived/used builds is required.

The other change I hope fits into the same bucket is strawman:fixing_override_mistake. Right now, because of pressure from test262, we are in danger of having all browsers conform to this mistake, at which point it may be too late to fix it. Today, the diversity of actual browser behaviors means it is still possible to fix this mistake, much as the diversity of ways ES3 implementations were broken made it possible for ES5 to fix many mistakes.

The [[CanPut]] check goes back to ES1, though. Recent-ish deviations in JSC and (because V8 was drafting off JSC) V8 don't nullify all that history.

On the other hand, JSC and V8 are doing fine AFAIK. It's hard to make a real-world case where this matters, even with Object.create. And I see the ocap (not just SES) appeal of the fix.

And there is one further issue I think is worth clearing up in email here.

What do we expect to be the normative status of the state machine itself and of the ES5 spec, after ES6 becomes official, for a browser that claims to be standards compliant, including ES6 compliant? AFAICT, no one has commented on this specifically, and we may be reading different assumptions into Allen's proposal.

What I am assuming Allen's proposal means is that the state machine would also be normative, and therefore those parts of the ES5 spec that are reachable from this state machine would remain normative as well. As I understand ECMA rules, it would be at least unusual for the earlier edition of the spec to remain normative even for systems claiming conformance to the later edition of the "same" spec. (same by spec number, i.e., 262).

I wrote in a previous reply that we aren't preserving ES5 as a spec referenced from ES6. ES6 will be self-contained. So I still don't grok your concern here.

# Mark S. Miller (12 years ago)

On Sun, Jan 8, 2012 at 10:32 AM, Brendan Eich <brendan at mozilla.com> wrote: [...]

All cool with the above. Thanks.

I wrote in a previous reply that we aren't preserving ES5 as a spec

referenced from ES6. ES6 will be self-contained. So I still don't grok your concern here.

Sorry, I missed that. In that case, I still don't understand what your plan for ES6 is. Does the ES6 spec include the state machine and an updated form of the ES5-non-strict portions of the ES5 spec, as referenced by that state machine? If not, what standards document governs how a future standards compliant browser is supposed to handle code which has non opted into anything, neither implicitly-explicitly nor explicitly?

# Brendan Eich (12 years ago)

On Jan 8, 2012, at 4:53 PM, Mark S. Miller wrote:

On Sun, Jan 8, 2012 at 10:32 AM, Brendan Eich <brendan at mozilla.com> wrote: [...]

All cool with the above. Thanks.

I wrote in a previous reply that we aren't preserving ES5 as a spec referenced from ES6. ES6 will be self-contained. So I still don't grok your concern here.

Sorry, I missed that. In that case, I still don't understand what your plan for ES6 is. Does the ES6 spec include the state machine and an updated form of the ES5-non-strict portions of the ES5 spec, as referenced by that state machine?

I defer to Allen, but one approach is to leave ES5-nonstrict as is, and combine strict and extended modes for the "6" in 5&6 and ES56. As you proposed!

HTH,

# Andreas Rossberg (12 years ago)

On 5 January 2012 20:10, Brendan Eich <brendan at mozilla.com> wrote:

On Jan 5, 2012, at 6:31 AM, Andreas Rossberg wrote:

Sorry, I still think that a growing set of subtle implicit rules for activating subtle semantic changes

Not changes, new semantics for new syntax.

I was referring to strict vs classic mode. The way I understood the discussion so far, certain syntax would implicitly opt into extended mode, and thereby also into strict mode -- locally, and from classic. That implies semantic changes for existing features.

[The discussion seems to have changed direction again with Allen's "state machine" idea. That probably makes some of my comments obsolete. Sorry for lagging behind.]

on a fine-grained level is far more confusing (and error-prone!) than helpful. In all sorts of ways.

Our experience was that adding new features to the default version where there was no backward incompatibility was not confusing. We've been doing this since 2006. Not to say all the particulars are right, or that we anticipated all the combinations of ES5-strict and Harmony, of course! But I think you protest too much without evidence.

Yes, but these didn't imply semantic changes to existing features, like with implicit strict mode opt-in.

I'm also concerned about the 3 and a half language modes that might result. With Dave's original proposal at least, the only opt-in was on module level.

Dave mentioned generators, IIRC. We were thinking of classes too (talked about it privately).

That precluded a number of highly undesirable combinations, e.g. extended mode nested into a "with" statement.

You can "use strict"; in a with statement's body block. But see below, I agree the opt in has to be "chunky", and is in the (not perfectly clear, complete, etc.) proposal for "ES6 doesn't need [version] opt-in".

We had been discussing opting in a function if it's using destructuring on its parameters, for example. I would count that as a case that is not "chunky" enough, because it can be local to "evil" classic features and e.g. screw up lexical scoping. Likewise generators. I really think we should avoid these cases.

[Now the idea seems to be that any ES6 feature would always opt-in the whole program. A like that much better, but it is not what Dave originally proposed, and what we had been discussing before, as far as I can tell.]

# Andreas Rossberg (12 years ago)

On 6 January 2012 03:37, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

Here is a possible set of rules for implicitly assigning ES5 or ES6 semantics to a Program unit

In an "ES6" implementation, all constructs that can occur in a valid program fit into one of these categories: ES6-only:  The construct is based upon syntax or static semantics rules that only exist in ES6.  For example, a destructuring pattern ES5-only:  The construct is based upon syntax that or static semantics rules that that are not in ES6. For example, use of a with statement t. ES5&ES6:  The construct has identical semantics in both ES5 and ES6. ES5~EAS6:  The construct has identical syntax and static semantics in both ES5 and ES6, but differing semantics.  For example, accessing a formal parameter after an assignment to the corresponding element of the function's arguments object.

We can then use the following state machines to describes the processing of a Program based upon the occurrence for these feature categories.  Initially start in State 5&6:

I think the state machine is over-complicating things. What it boils down to is that we are defining a new language, ES6-proper (or informally ES6 for short). It overlaps with ES5 but does not include it (e.g. throws out `with'). Then your "state machine" simply says, declaratively:

  • If a program is ES5 but not ES6, treat as ES5.
  • If a program is ES6 but not ES5, treat as ES6.
  • If a program is both ES5 and ES6, with identical semantics, treat as ES6 (although it doesn't matter).
  • If a program is both ES5 and ES6, with different semantics, treat as ES5 (for compatibility).
  • If a program is neither ES5 nor ES6, it's an error (obviously).

I very much like that opt-in would be for whole programs only. I think that is good, and IMHO progress over some of the earlier discussion.

But still, deciding what semantics a program has can depend on very minor syntactic details. This has several potential problems:

  1. For eternity, we will require future programmers to remember what exact features were introduced with ES6, in order to be sure that they trigger the right (i.e. strict) semantics for their program.

  2. The syntactic trigger in a given program may be hidden somewhere deep down the source code, which makes it very difficult to decide for the reader (and the compiler, which may have to do extra work in the parser to decide).

  3. Apparently minor local modifications may accidentally change the semantics of the whole program.

The obvious way to avoid all these issues is to always put an explicit trigger at the top. And I expect that style guides would recommend that. But that kind of defeats the purpose, doesn't it? I still haven't heard a convincing argument why it is advantageous to not make this explicit trigger mandatory.

# Claus Reinke (12 years ago)

But the new plan reminds me strongly of bad experiences in Haskell-land. That sounds interesting. Is this documented somewhere?

Not really documented, more my experience from having followed various Haskell lists and forums for over 15 years now (*).

I have often seen the pattern where a language or library design choice was discussed in length and detail in the appropriate sub-forum, then decided on, implemented, and rolled out, at first without a hitch, as long as it only affected those directly in the know; then, a couple of months later or so, the main forums would start seeing trouble reports from users who didn't use the new feature directly, but who where relying on code (applications/libraries) whose dependencies where affected by the change.

When Haskell implementations still had monolithic flags to enable all extensions at once, it would happen like this: some dependency D would not compile in standard mode, because it needed one of the extended language features, say F1; so extended mode had to be enabled for that dependency D, and sometimes for the whole project P; but, in extended mode, all extended features were enabled, including the one feature F2 that had recently been changed; enabling the new version of F2 would then interfere with compilation of either D or P, hopefully via an early error - compilation failure.

But the dependency D had been written long before F2 had been redesigned, and P itself had no direct uses of either F1 or F2, only via the dependency D. So the authors/maintainers of D and P had no idea of the discussion that led to the change of F2, they just saw an early error for code that used to work (and would still work, but for the fact that they had upgraded to newer implementations).

Similar fun ensued when authors started to use some extensions, but had to enable all extensions to make their code compile: in addition to the extended features they were working with, their code was now also affected by all other extended features, often expert-mode extensions that they had no idea about.

When Haskell implementations started using explicit feature-based versioning, thing calmed down considerably. Library authors would select the set of extensions they needed to use, and enable them only for their code (think of it as a specification of a language "API"). So neither their library code nor the code of library clients were affected by unrelated features (or changes to unrelated features).

And authors moving into language extensions would also enable them one by one, slowly building up to the level of complexity and expertise they needed. Blog posts would mention which extensions needed to be enabled to make examples run. When code used a language extension, the compiler would complain that this wasn't part of the portable standard, and suggest which pragma to use in order to enable just this one extension.

That doesn't mean that all problems are solved (eg, library versioning remains an interesting problem), but language extensions cause less trouble than they used to, and there is much less pressure for extending the language standard (Haskell 2010 is still very conservative, but a small flood of extensions are available, selectively).

Claus

(*) For background, the early history of Haskell is documented in this HOPL-III paper (no discussion of feature-based versioning, but describes the early language revisions, up to the long-time standard Haskell98, as well as some of the extensions, and the conflict between language design experimentation and stability):

"A History of Haskell: being lazy with class", 2007
http://research.microsoft.com/en-us/um/people/simonpj/papers/history-of-haskell/index.htm
# Brendan Eich (12 years ago)

On Jan 9, 2012, at 2:49 AM, Andreas Rossberg wrote:

On 5 January 2012 20:10, Brendan Eich <brendan at mozilla.com> wrote:

On Jan 5, 2012, at 6:31 AM, Andreas Rossberg wrote:

Sorry, I still think that a growing set of subtle implicit rules for activating subtle semantic changes

Not changes, new semantics for new syntax.

I was referring to strict vs classic mode. The way I understood the discussion so far, certain syntax would implicitly opt into extended mode, and thereby also into strict mode -- locally, and from classic. That implies semantic changes for existing features.

The question is how bad these will be for anyone writing JS naively, based on current and emerging (ES6) docs, without explicit opt-in.

The answer entails at least:

  1. ES5-strict semantic changes, e.g. arguments aliasing, without early errors.

  2. Completion reform.

  3. New early errors.

I think 3 is a good thing and a non-problem. Some of us hope 2 is a matter of indifference to real-world code, but we don't know for sure. That leaves 1.

No other semantic changes, right? The key idea of the state machine is not its exact spec (still being discussed) but that "one JS" should mean sane operation or early error, based on smooth upgrade to use non-conflicting new features (or conflicts, e.g. 'with' vs. 'module').

Why work harder on implicit opt-in? Yes, savvy users will put a pragma on line 1. Not all JS users are savvy and requiring them to become so asks too much.

[The discussion seems to have changed direction again with Allen's "state machine" idea. That probably makes some of my comments obsolete. Sorry for lagging behind.]

No problem.

# Brendan Eich (12 years ago)

On Jan 9, 2012, at 7:54 AM, Brendan Eich wrote:

The question is how bad these will be for anyone writing JS naively, based on current and emerging (ES6) docs, without explicit opt-in.

The answer entails at least:

  1. ES5-strict semantic changes, e.g. arguments aliasing, without early errors.

I should have written more, since "without early errors" is an assumption, not a requirement. Allen's state machine o.p discussed this more. From Allen's o.p.:

ES5~EAS6: The construct has identical syntax and static semantics in both ES5 and ES6, but differing semantics. For example, accessing a formal parameter after an assignment to the corresponding element of the function's arguments object.

This triggers different state transitions.

I'm working on SpiderMonkey now, and we already must analyze for arguments[i] and formal parameter assignments to deoptimize nonstrict code. The main challenge is good error blame when the compiler detects a conflict requiring an early error. You want to blame both the (possibly much earlier) formal or arguments[i] assignment, and the later ES6 feature.

I believe that all current optimizing JS engines have to do this kind of analysis. So I wonder if it wouldn't be "easy" (not sure how normative we make this) to deal with (1) with early errors.

# Andreas Rossberg (12 years ago)

On 9 January 2012 16:54, Brendan Eich <brendan at mozilla.com> wrote:

The question is how bad these will be for anyone writing JS naively, based on current and emerging (ES6) docs, without explicit opt-in.

The answer entails at least:

  1. ES5-strict semantic changes, e.g. arguments aliasing, without early errors.

  2. Completion reform.

  3. New early errors.

I think 3 is a good thing and a non-problem. Some of us hope 2 is a matter of indifference to real-world code, but we don't know for sure. That leaves 1.

Yes, but (1) is not a trivial set -- e.g. receiver coercions, eval semantics, delete type errors, arguments aliasing, poisoning of caller/arguments, etc.

No other semantic changes, right? The key idea of the state machine is not its exact spec (still being discussed) but that "one JS" should mean sane operation or early error, based on smooth upgrade to use non-conflicting new features (or conflicts, e.g. 'with' vs. 'module').

Why work harder on implicit opt-in? Yes, savvy users will put a pragma on line 1. Not all JS users are savvy and requiring them to become so asks too much.

But it's especially the non-savvy ones that would be particularly well-advised to put in that pragma, so that they avoid nasty surprises beyond their grasp!

# Brendan Eich (12 years ago)

On Jan 9, 2012, at 9:28 AM, Andreas Rossberg wrote:

On 9 January 2012 16:54, Brendan Eich <brendan at mozilla.com> wrote:

The question is how bad these will be for anyone writing JS naively, based on current and emerging (ES6) docs, without explicit opt-in.

The answer entails at least:

  1. ES5-strict semantic changes, e.g. arguments aliasing, without early errors.

  2. Completion reform.

  3. New early errors.

I think 3 is a good thing and a non-problem. Some of us hope 2 is a matter of indifference to real-world code, but we don't know for sure. That leaves 1.

Yes, but (1) is not a trivial set -- e.g. receiver coercions, eval semantics, delete type errors, arguments aliasing, poisoning of caller/arguments, etc.

You're right, there's a lot of runtime meaning shift in ES5-strict, indeed -- more than just parameter no-aliasing.

Still the bet we are trying to place (not done yet detailing the proposal) is that most people won't use explicit version-based opt-in (also, such noise gets lost easily over the life of code snippets), and that the hard cases you cite are rare. None is common.

# Allen Wirfs-Brock (12 years ago)

On Jan 8, 2012, at 9:26 PM, Brendan Eich wrote:

On Jan 8, 2012, at 4:53 PM, Mark S. Miller wrote:

On Sun, Jan 8, 2012 at 10:32 AM, Brendan Eich <brendan at mozilla.com> wrote: [...]

All cool with the above. Thanks.

I wrote in a previous reply that we aren't preserving ES5 as a spec referenced from ES6. ES6 will be self-contained. So I still don't grok your concern here.

Sorry, I missed that. In that case, I still don't understand what your plan for ES6 is. Does the ES6 spec include the state machine and an updated form of the ES5-non-strict portions of the ES5 spec, as referenced by that state machine?

I defer to Allen, but one approach is to leave ES5-nonstrict as is, and combine strict and extended modes for the "6" in 5&6 and ES56. As you proposed!

HTH,

/be

I've been thinking about this, but I'm not yet certain about a preferred approach.

One alternative is to keep the current ES5 specification( both non-strict and strict) as a normative part of ECMA-262 and add a new part which is the complete specification for "ES6". Essentially Ecma-262-6 part 1 would be the same as Ecma-262-5 (plus any errata level corrections) but excluding most of clause 15 (the builty-in library) and Ecma-262-9 part 2 would be a comprehensive specification for a language that starts with implicit ES5 strict mode (modulo completion reform and any other semantic changes) add new ES6 features and excludes ES5 "non-strict" features and semantics. Because of the shared heap, the new clause 15 would have to apply to both the part 1 and part 2 languages. The specification would have to include something like my state machine which determines whether the part 1 or part 2 language is to be used to process a Program.

This approach has the advantage that it simplifies the specification of the new ES6 features and minimizes the risk of unintentionally changing the specification of ES-5 non-strict features that must exist somewhere in the specification. I'm finding various places where significant changes in specification technique is required to support new feature semantics and making sure that the rewritten specification also works for legacy (non-strict ES5). However, this approach has the disadvantage that implementors who what to share as much logic as possible between "ES5 mode" and "ES6 mode" many have to do their own analysis of the differences and commonalities.

# Gavin Barraclough (12 years ago)

On Jan 9, 2012, at 2:59 AM, Andreas Rossberg wrote:

I think the state machine is over-complicating things. What it boils down to is that we are defining a new language, ES6-proper (or informally ES6 for short). It overlaps with ES5 but does not include it (e.g. throws out `with'). Then your "state machine" simply says, declaratively:

  • If a program is ES5 but not ES6, treat as ES5.
  • If a program is ES6 but not ES5, treat as ES6.
  • If a program is both ES5 and ES6, with identical semantics, treat as ES6 (although it doesn't matter).
  • If a program is both ES5 and ES6, with different semantics, treat as ES5 (for compatibility).
  • If a program is neither ES5 nor ES6, it's an error (obviously).

If the a program is both ES5 and ES6 with identical semantics, then presumably we could equally treat it as ES5 with no behavior change?

If so, couldn't this be stated in a much simpler fashion:

  • If a program is ES5, treat as ES5.
  • If a program is not ES5 but is ES6, treat as ES6.
  • If a program is neither ES5 nor ES6, it's an error (obviously).
# Allen Wirfs-Brock (12 years ago)

On Jan 8, 2012, at 10:32 AM, Brendan Eich wrote:

On Jan 8, 2012, at 8:28 AM, Mark S. Miller wrote: ...

The other change I hope fits into the same bucket is strawman:fixing_override_mistake. Right now, because of pressure from test262, we are in danger of having all browsers conform to this mistake, at which point it may be too late to fix it. Today, the diversity of actual browser behaviors means it is still possible to fix this mistake, much as the diversity of ways ES3 implementations were broken made it possible for ES5 to fix many mistakes.

The [[CanPut]] check goes back to ES1, though. Recent-ish deviations in JSC and (because V8 was drafting off JSC) V8 don't nullify all that history.

On the other hand, JSC and V8 are doing fine AFAIK. It's hard to make a real-world case where this matters, even with Object.create. And I see the ocap (not just SES) appeal of the fix.

Just to be even clearer. This was not a mistake in ES5/5.1 and it is not a bug. It is a semantics, which as Brendan points out goes all the way back to ES1. It is also a behavior which makes complete sense from a prototypal inheritance perspective and can be found in the Self language.

The basic idea is that the properties prototype object are shared parts of all of inheriting child object. Modifying such a shared part by a child, introduces a local change that is visible to that child (and its children) so this requires creation of a "own" property on the child. However, read-only properties can not modified (by normal means, eg assignment) so there is no need to create a "own" copy. Assigning to an inherited read-only property or a "own" read-only property should have the same affect (whether it is ignoring the assignment, throwing, etc.). Allowing assignment to an inherited read-only property would break the invariant that that a prototype's readonly property is an immutable value that is shared among all children of the prototype.

If there was a mistake in designing ES5, it was allowing Object.defineOwnProperty to create child properties that over-ride inherited read-only data properties. This broke an invariant that previously existed in the language but this invariant was already violated by some pre-ES5 clause 15 objects, (eg the writability of the prototype property of some children of Function.prototype). However, I think the ES5 decision was probably the right one given the legacy clause 15 usages and the overall reflective nature of defineOwnProperty).

# Brendan Eich (12 years ago)

FTR (a broken record, sorry), I think we will do a big disservice to interoperation in practice (as enjoyed by future web devs) if we essentially fork the spec and mutate one copy (even excluding Clause 15) to be ES6.

I'm still pretty sure implementations will not fork their non-library codebases. Mozilla's won't. So that means the spec will be the only unconsolidated "implementation".

Yes, new features (especially around parameters) may combine with old (arguments). Let's do the case analysis and see how bad this must be. I think it's strictly less bad on balance, weighing the cost to implementors, than forking.

Forking the spec also raises the risk of more incompatible changes than those (few, almost none) that we intended. I'd rather bail on completion reform back to ES5 strict runtime semantics if that is what it takes to keep a consolidated/minimized spec.

# Andreas Rossberg (12 years ago)

On 9 January 2012 21:37, Gavin Barraclough <barraclough at apple.com> wrote:

On Jan 9, 2012, at 2:59 AM, Andreas Rossberg wrote:

I think the state machine is over-complicating things. What it boils down to is that we are defining a new language, ES6-proper (or informally ES6 for short). It overlaps with ES5 but does not include it (e.g. throws out `with'). Then your "state machine" simply says, declaratively:

  • If a program is ES5 but not ES6, treat as ES5.
  • If a program is ES6 but not ES5, treat as ES6.
  • If a program is both ES5 and ES6, with identical semantics, treat as ES6 (although it doesn't matter).
  • If a program is both ES5 and ES6, with different semantics, treat as ES5 (for compatibility).
  • If a program is neither ES5 nor ES6, it's an error (obviously).

If the a program is both ES5 and ES6 with identical semantics, then presumably we could equally treat it as ES5 with no behavior change? If so, couldn't this be stated in a much simpler fashion:

  • If a program is ES5, treat as ES5.
  • If a program is not ES5 but is ES6, treat as ES6.
  • If a program is neither ES5 nor ES6, it's an error (obviously).

Indeed, that is even more to the point.

# Herby Vojčík (12 years ago)

This is interesting issue. There is a subtle difference between "prototype chain is the shared part" Self mindset and the "prototype chain is fallback delegation" mindset. Though I knew of Self and knew it had an impact on Javascript creation, I had always an impression that in Javascript (having become ECMAScript) it was the latter, that is the philosophy is that child can override the default from prototype chain.

So what is the actual philosophy of ES prototype chain?

Herby

-----Pôvodná správa---

# Herby Vojčík (12 years ago)

Hello again!

Sorry to reply my own post, but I came to the conclusion that "Self-like shared part" simply cannot work as an argument (and that not to be able to override read-only property from the prototype is, indeed, an error).

If the "shared part" mind-set was the cornerstone of ES, then this:

foo = { x:4 }; bar = Object.create(foo); bar.x = 5; return foo.x;

would yield 5. After all, x is shared part of bar and foo.

But every knows that it yield 4. bar has its own x holding 5, foo has his own holding 4. So as for this:

=== Allen Wirfs-Brock wrote === The basic idea is that the properties prototype object are shared parts of all of inheriting child object. Modifying such a shared part by a child, introduces a local change that is visible to that child (and its children) so this requires creation of a "own" property on the child. However, read-only properties can not modified ...

I can only reply with "if the prototype is the shared part as per Self mindset, you cannot create "own" property at all". In previous example, x is the shared part. Above statement bar.x = 5 would change foo.x (since it is the shared x).

So I think the model in Javascript is "fallback delegation" and creation of own property by Object.defineProperty is not an error; and creation of own property by assignment should be allowed; and inability to do it is indeed an error.

Herby

-----Pôvodná správa---

# Andreas Rossberg (12 years ago)

On 9 January 2012 21:41, Allen Wirfs-Brock <allen at wirfs-brock.com> wrote:

On Jan 8, 2012, at 10:32 AM, Brendan Eich wrote:

On Jan 8, 2012, at 8:28 AM, Mark S. Miller wrote: ...

The other change I hope fits into the same bucket is strawman:fixing_override_mistake. Right now, because of pressure from test262, we are in danger of having all browsers conform to this mistake, at which point it may be too late to fix it. Today, the diversity of actual browser behaviors means it is still possible to fix this mistake, much as the diversity of ways ES3 implementations were broken made it possible for ES5 to fix many mistakes.

The [[CanPut]] check goes back to ES1, though. Recent-ish deviations in JSC and (because V8 was drafting off JSC) V8 don't nullify all that history.

On the other hand, JSC and V8 are doing fine AFAIK. It's hard to make a real-world case where this matters, even with Object.create. And I see the ocap (not just SES) appeal of the fix.

Just to be even clearer.  This was not a mistake in ES5/5.1 and it is not a bug.  It is a semantics, which as Brendan points out goes all the way back to ES1.  It is also a behavior which makes complete sense from a prototypal inheritance perspective and can be found in the Self language.

I admit being almost completely ignorant about the history, but I always found this behaviour more than weird. In an ideal world, shouldn't lookup and immutability be two orthogonal mechanisms? That is, either all assignments override (data properties), or none.

The basic idea is that the properties prototype object are shared parts of all of inheriting child object.  Modifying such a shared part by a child, introduces a local change that is visible to that child (and its children) so this requires creation of a "own" property on the child. However, read-only properties can not modified (by normal means, eg assignment) so there is no need to create a "own" copy.  Assigning to an inherited read-only property or a "own" read-only property should have the same affect (whether it is ignoring the assignment, throwing, etc.).  Allowing assignment to an inherited read-only property would break the invariant that that a prototype's readonly property is an immutable value that is  shared among all children of the prototype.

Can you elaborate why you are making a special case for immutable properties? Allowing overriding of a writeable property likewise breaks the sharing "invariant". Why should it apply to read-only properties specifically?

Your argument, if I understand it correctly, is that immutability is part of the contract of a prototype, and that contract should be inherited by children. Informally, that contract says:

Reading this property, through the prototype or any of its children, always returns the same value. Writing to it is an error.

But what is the corresponding contract of a mutable property? AFAICT, it is:

Reading this property, through the prototype or any of its children, always returns the last value that has been written to it.

And that is broken by overriding in the same way as the contract for immutable properties.

# John J Barton (12 years ago)

On Tue, Jan 10, 2012 at 5:38 AM, Herby Vojčík <herby at mailbox.sk> wrote:

Hello again!

Sorry to reply my own post, but I came to the conclusion that "Self-like shared part" simply cannot work as an argument (and that not to be able to override read-only property from the prototype is, indeed, an error).

If the "shared part" mind-set was the cornerstone of ES, then this:

foo = { x:4 }; bar = Object.create(foo); bar.x = 5; return foo.x;

would yield 5. After all, x is shared part of bar and foo.

But every knows that it yield 4. bar has its own x holding 5, foo has his own holding 4. So as for this:

=== Allen Wirfs-Brock wrote ===

The basic idea is that the properties prototype object are shared parts of all of inheriting child object.

In your example |foo| is not an inheriting child object. It is the parent.

jjb

# Herby Vojčík (12 years ago)

=== In your example |foo| is not an inheriting child object. It is the parent.

jjb

Yes. It is correct. This is what the subdiscussion is about, is it not? Allen said it is not an error to prohibit bar.x = 5 if foo.x is read-only and argument that foo.x (foo is parent, bar is child) is shared part.

And I'm saying it is falsy to argument with "x is shared part of bar through parent foo" because then bar.x - 5 must change foo.x but it does not. Creating "own" properties is breaking "shared part". Since there are "own" shadowing properites, the model must not be "shared part", but something other, which allows "own", shadowing properties. In such a model it makes perfect sense to allow bar.x = 5 without regard to foo.x readonliness. Prohibiting it is in fact something that is inconsistent with the mental model.

So, to sum, either we have "self shared part" mental model, in which foo.x is shared part of bar.x, but then you cannot have "own" shadowing preoperties and plain "bar.x = 5" behaviour of current implementations is inconsistent; or we have "own overlaying properties with property chain search" (which I dubbed "fallback delegation") mental model, in which bar.x = 5 make perfect sense, but alas not only in writable foo.x scenario, but every time (even if foo.x does not exist or if foo.x is read-only), so prohibiting it for read-only foo.x is inconstitency.

So, we have inconsistency anyway, the question is which one to fix (and I am pretty sure not the first one).

Herby

P.S.: Yes, you can construct a complicated mental model amalgamate, like "shared part with explicit allowance for per-child-subtree-shadowing". Let someone like Allen or Brendan tells exactly what is the mental model, then. It seems none of simple, consistent models of "shared part" or "fallback delegate" is true.

-----Pôvodná správa---

# Herby Vojčík (12 years ago)

Hello,

so I looked up the spec. I never remembered the exact [[Put]], [[Get]] machinery but now it is obvious that ES has in fact "amalgamate" mental model of "shared-part always-use-setter-for-assignment having shadowing default setter" (well, I figured that out before looking at spec by just reverse engineering actual state, but then I looked up). So, really, strawman:fixing_override_mistake is not an error. But so is not the Object.defineProperty that is able to create own property. Assignment is assignment, setting own property is different, low-level thing. At least as I see it.

Herby

P.S.: I would bet 99% of developers thinks the model is in fact "fallback delegation". :-/ It is simpler model that works most of the time. Always write locally, always read locally and then look up the prototype chain.

-----Pôvodná správa---

# liorean (12 years ago)

2012/1/10 Herby Vojčík <herby at mailbox.sk>:

P.S.: I would bet 99% of developers thinks the model is in fact "fallback delegation". :-/ It is simpler model that works most of the time. Always write locally, always read locally and then look up the prototype chain.

I think that's a question of making a fallacy of equivocation. The localisation when reading from or writing to a property is a matter of the value contained in that slot. The access or write permission of that property is a matter of the actual slot.