ES6 opt-in, reloaded

# Andreas Rossberg (14 years ago)

Some of the recent discussion regarding ES6 opt-in has been rather... scary. I would like to step back a bit, and try to identify the goals that we actually want to achieve. I think these are:

  1. Make ES6 opt-in as convenient as possible.

  2. Avoid reliance on external features like script tags or mime types. (Not all JS is on web pages anyway!)

  3. Provide as many incentives for switching to ES6 as possible.

  4. Minimize necessary changes in programming style.

  5. Minimize ES5-ES6 transitioning pitfalls.

  6. Avoid maintenance or refactoring pitfalls in ES6 itself. Avoid that ES6 programmers have to think about modes or feature sets at all. (This particularly should apply to non-savvy programmers!)

  7. Minimize semantic complications (which tend to harm both implementations and programmers).

  8. Obviously, achieve full backwards compatibility.

The various proposals in the thread were primarily aimed at (1), but AFAICS all fail on (6), to different extent. (There were mixed results for the other points.) Consequently, programmers would have to be aware which features put them into ES6 (and thus strict mode), and which are only available there, and that local changes could subtly change the meaning of unrelated program parts (due to strict mode), or even the whole program. Moving code could subtly change its internal meaning.

It's also worth noting that backporting new ES6 features to classic mode, as has been proposed by several people, clearly works against (3), and consequently, also against (5/6). Similarly, opting in at smaller scope, as has also been discussed, is a blatant violation of (6) and (7), and works against (3), too.

So I have plea: let's keep it simple. Start from the simplest of all possible ideas and optimize from there. That is, opting into ES6 through one simple declaration on the top of a program (and nowhere else). Otherwise it's ES5 unchanged. As far as I can see, that clearly wins, or is no worse than other proposals, on anything but (1).

Compared to Dave's original proposal, the only scenario on which it loses re (1) is if the program already consists of a module, in which case you'd still have to write the opt-in declaration. One can probably argue whether this is worth adding an extra rule (personally, I don't think so). If so, it would be enough to say that a module declaration as the first statement in the program also opts in all of the program.

In either case:

  1. You always opt in all of the program consistently.
  2. It is obvious from the first line that you do.
  3. This truly opts-in the toplevel (for whatever that means in detail).
  4. A future ES programmer just needs to know one rule: start your program with _____.

(I'm intentionally avoiding bike-shedding a concrete syntax for ____ here.)

And before somebody brings it up: I do not fear that this will share the fate of strict mode, because there actually is plenty of incentive for using ES6 (especially, if we do not extend classic mode).

# Luke Hoban (14 years ago)

Great summary of the goals, and I agree with the plea for starting with "opting into ES6 through one simple declaration on the top of a program (and nowhere else). Otherwise it's ES5 unchanged."

.#6 has other variants as well, such as copy/paste hazards and even subtler concatenation hazards, which are the kinds of issues that will be common for breadth JavaScript developers.

Luke

# Axel Rauschmayer (14 years ago)

Compared to Dave's original proposal, the only scenario on which it loses re (1) is if the program already consists of a module, in which case you'd still have to write the opt-in declaration. One can probably argue whether this is worth adding an extra rule (personally, I don't think so). If so, it would be enough to say that a module declaration as the first statement in the program also opts in all of the program.

In either case:

  1. You always opt in all of the program consistently.
  2. It is obvious from the first line that you do.
  3. This truly opts-in the toplevel (for whatever that means in detail).
  4. A future ES programmer just needs to know one rule: start your program with _____.

+1

My main concern: things should be dead simple to learn and/or explain. Andreas’ proposal is, what has been discussed so far might also eventually be, but I don’t see it (yet).

Claus Reinke’s recount of lessons learned by the Haskell community in this area also seems relevant: esdiscuss/2012-January/019396

Axel

# Brendan Eich (14 years ago)

Andreas Rossberg <mailto:rossberg at google.com> January 16, 2012 8:09 AM Some of the recent discussion regarding ES6 opt-in has been rather... scary.

It's es-discuss, don't be scared. Just working through a thought-experiment, however misguided in someone's view, is low-cost and needs a forum for discussion. Occasionally we are all mistaken about an idea being misguided, and the experiment will prove worth the cost of discussion.

I would like to step back a bit, and try to identify the goals that we actually want to achieve. I think these are:

  1. Make ES6 opt-in as convenient as possible.

Yes. We need to talk about what actually has happened on the web with opt-in attributes and modifiers, though -- not just try to build a minimized logic system that we think is convenient (mostly because it is minimized).

  1. Avoid reliance on external features like script tags or mime types. (Not all JS is on web pages anyway!)

Just to be clear, the RFC4329 version parameter was never proposed as sufficient. It or something equivalent in the script type attribute is necessary on the web to hide new script from old browsers.

  1. Provide as many incentives for switching to ES6 as possible.

This is a job for ES6 itself, not just its opt-in system.

  1. Minimize necessary changes in programming style.

Taken one way, this excludes new semantics, since people have to build everything today using objects and closures. Making a virtue of necessity could kill almost any proposal (destructuring, generators, modules -- even let).

It's not clear how listing several variables to minimize helps if we don't have a trade-off formula against opportunity costs and cost of building everything more or less in today's style.

  1. Minimize ES5-ES6 transitioning pitfalls.

Five fingers of fate. Only one used right now.

  1. Avoid maintenance or refactoring pitfalls in ES6 itself. Avoid that ES6 programmers have to think about modes or feature sets at all. (This particularly should apply to non-savvy programmers!)

Amen -- this is the impetus for the "scary" discussion.

  1. Minimize semantic complications (which tend to harm both implementations and programmers).

The Harmony page talks about doing this by desugaring where possible, extending kernel semantics only where necessary and with primitives that compose well.

This implies that a new, consolidated Harmony-era spec must define how extended semantics interact with strict semantics at least. But we may have assumed that we didn't need to define interactions with non-strict semantics. That raises the question of opt-in before us.

My point is that if opt-in usability demands defining extended vs. non-strict complications, then so be it. We should take the hit. Again minimizing all of 4 or 7 variables is impossible. There are trade-offs. Judging them well is an art and it does depend on usability in the large (your goals 1 and 6 may well trump 7).

  1. Obviously, achieve full backwards compatibility.

We've considered marginally breaking changes, e.g. completion reform. We should discuss this one more if necessary.

The various proposals in the thread were primarily aimed at (1), but AFAICS all fail on (6), to different extent. (There were mixed results for the other points.)

Agree that 1 vs. 6 is the fight to focus on here.

Consequently, programmers would have to be aware which features put them into ES6 (and thus strict mode), and which are only available there, and that local changes could subtly change the meaning of unrelated program parts (due to strict mode), or even the whole program. Moving code could subtly change its internal meaning.

You made some good points already in the thread against this, phrased in terms of those strict-vs.non-strict semantic shifts (eval of var, arguments aliasing, a few others -- not terribly many). I'm happy to concede (I thought I did already) that was a mortal blow to "tainting" implicit opt-in where a Harmony feature at the last line of 60,000 would put the whole large program in strict mode.

Let's agree on this if we can (Allen may not).

It's also worth noting that backporting

That is a loaded verb-form ("backporting"). Who says there is a separate older spec or implementation to which to port?

Yes, analyzing interactions requires reading the ES5 spec in non-strict mode. No, the result is not necessarily a port -- it's a patch (if you insist on hacking metaphors).

new ES6 features to classic mode, as has been proposed by several people, clearly works against (3),

I object. 3 is misstated to assume "switching" means all-or-nothing. If ES6 has new features and they are worth using, developers will want to use them piecewise and conveniently. Assuming they will do so at the price of a leading "use version 6" or equivalent pragma is debatable. We shouldn't just assume this conclusion.

I think 3 should be stated as "Provide as many incentives for using ES6 as possible". Here "using" does not demote making use of every last feature in every script. There's no need to require "switching" and it may be user-hostile to do so.

and consequently, also against (5/6).

This does not follow. Users transitioning to use new features may be helped, not hindered, by we spec and implementation editors working harder. In particular allowing, e.g., destructuring in non-strict code may be boon to developers, whereas requiring them to maintain explicit opt-in pragmas before doing so simply to convenience spec editors and implementors may be a global loss.

The game theory is multi-party and the moral hazard for us as spec and implementation people is to optimize for our own convenience or assert that it's identical to developer usability. It's not.

Similarly, opting in at smaller scope, as has also been discussed, is a blatant violation of (6) and (7), and works against (3), too.

Let's agree, based on your earlier arguments and also on Dave's point that lexical scope (free variable error analysis) won't work if opt-in is too local. This still does not mean that all extensions must be supported only under all-or-nothing early opt-in.

So I have plea: let's keep it simple. Start from the simplest of all possible ideas and optimize from there. That is, opting into ES6 through one simple declaration on the top of a program (and nowhere else). Otherwise it's ES5 unchanged.

You are arguing based on "simplest" without considering real-world usability. Too much a-priori system building makes a crashed sky castle, I've seen this over and over. On the web, a-posteriori wins.

On the web, people lose version opt-in pragmas and MIME type versions (Larry Masinter made this point about the latter but it applies to the former). On the web, we have "use strict"; in the middle of concatenations. We have Unicode BOMs in the middle. We have

<!-- hide script from old browsers

and

// -->

and even

-->

in the middle of .js files!

No plan survives contact with the enemy (Moltke) comes to mind.

Meanwhile, JS1 grew via ES1-5 and various implementations (SpiderMonkey and Rhino) without any such opt-in, except for two keywords we couldn't reserve (and some incompatible change attempts that failed, vs. goal 8 -- one example: I tried to make == and != strict in JS1.2 to get ES1 to switch, but that broke the web, wherefore === and !==).

As far as I can see, that clearly wins, or is no worse than other proposals, on anything but (1).

I'm sure it wins for spec authors and implementors -- forking large parts of the spec might even "win" in the short term. These would all be bad for users, IMHO.

Compared to Dave's original proposal, the only scenario on which it loses re (1) is if the program already consists of a module, in which case you'd still have to write the opt-in declaration. One can probably argue whether this is worth adding an extra rule (personally, I don't think so).

So I'd have to write

use version 6; module {...}

instead of just

module {...}

even though there's no backward compatibility issue? That's just mean! :-|

If so, it would be enough to say that a module declaration as the first statement in the program also opts in all of the program.

Why first if (as Dave's proposal in its minimal form did) a later module cannot affect earlier non-strict top level semantics? Again there is no backward compatibility issue. Why, as spec writer or implementor, do you care whether it's

<script> // non-strict top level code </script> <script> module {...} </script>

or the concatenation?

<script> // non-strict top level code module {...} </script>

Nothing in the spec or an optimizing implementation, if we assume module M is its own opt-in such that ... is in ES6, should care when processing the non-strict top level code.

In either case:

  1. You always opt in all of the program consistently.
  2. It is obvious from the first line that you do.

First lines get lost or moved down all the time.

Making this an early error helps only assuming diligent testing in new implementations. That's not safe to assume.

  1. This truly opts-in the toplevel (for whatever that means in detail).
  2. A future ES programmer just needs to know one rule: start your program with _____.

(I'm intentionally avoiding bike-shedding a concrete syntax for ____ here.)

Yeah, it's ok -- but let's not keep cleaning the slate. We have RFC4329 and browsers do hide new version= values. We have a proposal lurking about for "use version 6". Avoiding bikeshedding is one thing, avoiding usability testing and assuming the first-line selector wins is not justified.

And before somebody brings it up: I do not fear that this will share the fate of strict mode, because there actually is plenty of incentive for using ES6 (especially, if we do not extend classic mode).

Another assumption. There may be more carrots this time, but if the explicit opt-in requirement is too unusable in practice, then ES6 may bounce, or simply have slower adoption that we'd like, ceteris paribus (many variables here, hard to project -- let's avoid arguing based on our favored speculations).

My non-speculative argument rests on experience on the web. Version marks and other separators are mangled and lost all the time.

I believe we should offer well-defined <script type=...;version=...>

opt-in for script content hiding from old UAs, and in addition belt-and-braces in-script-content version opt-in at a minimum, via some pragma such as |use version 6;|.

But I also believe these will be lost or simply not used, and adoption will be better if we define sensible (per your arguments against tainting from small to large extent) new-syntax-is-its-own-opt-in rules, per Dave's o.p.

The reason I believe this is our experience in past edition shifts and experimental extensions. Usability wins should tax spec-editors (if not implementors -- let's not assume the implementation costs of implicit opt-in are too high [*]) for the greater good of the JS developers.

/be

[*] SpiderMonkey has no runtime tests based on <script type="...; version=X"> values, if I'm not mistaken, and very few compile-time. And

this is with quite a few extensions, including standard ones such as E4X. Yes, we learned some lessons at non-trivial cost, but these need not be re-learned from scratch by TC39.

# Brendan Eich (14 years ago)

Brendan Eich <mailto:brendan at mozilla.org> January 16, 2012 10:35 AM

I think 3 should be stated as "Provide as many incentives for using ES6 as possible". Here "using" does not demote making

Typo: s/demote/denote/ -- sorry!

# David Herman (14 years ago)

On Jan 16, 2012, at 8:09 AM, Andreas Rossberg wrote:

  1. Make ES6 opt-in as convenient as possible.

  2. Avoid reliance on external features like script tags or mime types. (Not all JS is on web pages anyway!)

  3. Provide as many incentives for switching to ES6 as possible.

  4. Minimize necessary changes in programming style.

  5. Minimize ES5-ES6 transitioning pitfalls.

  6. Avoid maintenance or refactoring pitfalls in ES6 itself. Avoid that ES6 programmers have to think about modes or feature sets at all. (This particularly should apply to non-savvy programmers!)

  7. Minimize semantic complications (which tend to harm both implementations and programmers).

  8. Obviously, achieve full backwards compatibility.

The goal that is missing, and what I believe is the single most important part of my New Year's email, is:

  1. Allow programmers to continue thinking of JS as a single cohesive language.

This is a relative concept rather than an absolute one, but it has real practical consequences on the overall cognitive complexity of the language. If you look at the terminology we've been using in TC39 for years, we either talk about multiple "modes" of JS, or even talk about ES6 as if it's "the new language." I believe this has been a mistake. Now, we've always agreed that ES6 must necessarily subsume ES5. And you might claim that this was just imprecise and casual speech, but I think it betrays a flawed assumption: that ES6 gives us leeway to make clean breaks with the past.

ES6 should not fork JS into two separate and (over time) diverging dialects. The cost to developers would be too great. Ask a web developer, and I bet you 9 times out of 10 they will agree. Certainly the ones I've talked to so far do.

We still want to do the best we can to make some changes that cannot be made compatibly without a distinguishing syntactic context, which is why I believe tying those changes to module is the best way forward. But those changes should simply mean a few cleanups within a distinguished context of the same overall language.

On top of that, I believe that any explicit opt-in is simply too high a price to pay. People will often not use it in <script> tags, and they will certainly not use it in inline HTML attributes like onclick="...". If we are more modest in our predictions of the future, and tie the cleanups to an attractive feature like modules, then people will naturally gravitate towards the cleaner language whenever they want to use modules. But we should not be so idealistic as to ever predict that "everyone will eventually learn to opt in to the new language" simply because we say it's better. That's just too big a bet.

So if we can't predict that everyone will jump with both feet into a new dialect, we have to make sure that the changes we make for the new context are not so big as to cause impedance mismatches between old-style code and new-style code. This way people can generally migrate code back and forth between the contexts without too many surprises. Yes, if they're exploring the bizarre cases of JS, like aliasing of arguments and calling methods as functions, they can still get bit. It won't be perfect. But if, by contrast, we keep expanding the differences by needlessly keeping new features barred from the old context, programmers will be stuck forever having to learn about the many differences between the two dialects, and the spec will be stuck with a growing chasm between two sub-languages of ES.

Instead, we should hew as close as possible to an ideal of One JavaScript, knowing that we'll never be perfect. But -- I believe -- we can get pretty darn close.

# Brendan Eich (14 years ago)

David Herman <mailto:dherman at mozilla.com> January 16, 2012 2:18 PM

The goal that is missing, and what I believe is the single most important part of my New Year's email, is:

  1. Allow programmers to continue thinking of JS as a single cohesive language.

Thanks, this is the transcendant usability goal in my view. ES4 suffered for many reasons, and seeming to create too different a language was one of its biggest conceptual (as in problem in the abstract, not just in concrete proposals) flaws. Not all of this was bad PR, either.

If we have already gone too far in terms of runtime "five fingers of fate" changes with completion reform, we can pare back. But I'd like to give c.r. a chance -- it may be non-breaking.

New compile-time reforms such as modules making free variable typos be early errors are a feature, and the fact that they're early errors is pure unadulterated win.

New early errors (provided they are not prone to false positives or attempts to enfoce style on which reasonable people disagree) seem all good. Early errors under new syntax are not a forking of the language from being One JavaScript.

Version opt-in goes the other way, not just as a user-facing requirement to use an early top level pragma, but also in terms of learning, thinking, and further evolving the language. This is a risk for spec editors and TC39 -- I believe strongly that we must not fork the spec.

# David Herman (14 years ago)

On Jan 16, 2012, at 2:47 PM, Brendan Eich wrote:

If we have already gone too far in terms of runtime "five fingers of fate" changes with completion reform, we can pare back. But I'd like to give c.r. a chance -- it may be non-breaking.

Yes, absolutely. I believe it's got non-breaking potential. I'm not sure but it might even be OK to make the change in all contexts.

# Allen Wirfs-Brock (14 years ago)

One of the thing that an "ES6" (or "extended code" as it is currently known in the ES6 draft) mode that is distinct from "strict mode" does is allow us do add additional Early Errors that aren't in ES5 for various existing constructs. It isn't clear that we could continue to have add these errors if we don't have this "mode" distinction as they are generally potentially breaking changes. Just to see what the impact of that might be I've extracted all such error that are in current ES6 draft. Note that this list is not necessarily exhaustive as there may well be additional such errors that would apply to parts of the spec. that I haven't worked on yet.

PropertyAssignment : get PropertyName () { FunctionBody }

• It is a Syntax Error if the source code matching this production is extended code and the PropName of PropertyName also occurs in the VarDeclaredNames of FunctionBody. • It is a Syntax Error if the source code matching this production is extended code and the PropName of PropertyName also occurs in the LexicallyDeclaredNames of FunctionBody.

PropertyAssignment : set PropertyName ( PropertySetParameterList ) { FunctionBody }

• It is a Syntax Error if the source code matching this production is extended code and the PropName of PropertyName also occurs in the VarDeclaredNames of FunctionBody. • It is a Syntax Error if the source code matching this production is extended code and the PropName of PropertyName also occurs in the LexicallyDeclaredNames of FunctionBody. • It is a Syntax Error if the source code matching this production is extended code and any element of the LexicallyDeclaredNames of PropertySetParameterList also occurs in the VarDeclaredNames of FunctionBody. • It is a Syntax Error if the source code matching this production is extended code and any element of the BoundNames of PropertySetParameterList also occurs in the LexicallyDeclaredNames of FunctionBody.

PostfixExpression : LeftHandSideExpression [no LineTerminator here] ++ LeftHandSideExpression [no LineTerminator here] --

• It is a Syntax Error if the AssignmentExpression is contained in extended code and the LeftHandSideExpression is a Literal or a FunctionExpression. • It is a Syntax Error if the AssignmentExpression is contained in extended code and the LeftHandSideExpression is an Identifier that does not statically resolve to a declarative environment record binding or if the resolved binding is an immutable binding. UnaryExpression : delete UnaryExpression ++ UnaryExpression -- UnaryExpression

• It is a Syntax Error if the UnaryExpression is contained in extended code and the derived UnaryExpression is a Literal or a FunctionExpression. • It is a Syntax Error if the UnaryExpression is contained in extended code and the derived UnaryExpression is an Identifier that does not statically resolve to a declarative environment record binding or if the resolved binding is an immutable binding. AssignmentExpression : LeftHandSideExpression = AssignmentExpression LeftHandSideExpression AssignmentOperator AssignmentExpression

• It is a Syntax Error if the AssignmentExpression is contained in extended code and the LeftHandSideExpression is a Literal or a FunctionExpression. • It is a Syntax Error if the AssignmentExpression is contained in extended code and the LeftHandSideExpression is an Identifier that does not statically resolve to a declarative environment record binding or if the resolved binding is an immutable binding.

Block : { StatementList }

• It is a Syntax Error if StatementList includes a StatementListItem : Declaration production whose Declaration is a Declaration : FunctionDeclaration production and the source code matching this Block production is not contained in extended code. Block : { StatementList }

• It is a Syntax Error if StatementList includes a StatementListItem : Declaration production whose Declaration is a Declaration : FunctionDeclaration production and the source code matching this Block production is not contained in extended code. Catch : catch ( CatchParameter ) Block

• It is a Syntax Error if the code that matches this production is contained in extended code and any element of the BoundNames of CatchParameter also occurs in the VarDeclaredNames of Block. FunctionDeclaration : function BindingIdentifier ( FormalParameterList ) { FunctionBody } and FunctionExpression : function BindingIdentifieropt ( FormalParameterList ) { FunctionBody }

• It is a Syntax Error if the source code matching this production is extended code and the BoundNames of BindingIdentifier also occurs in the VarDeclaredNames of FunctionBody. • It is a Syntax Error if the source code matching this production is extended code and the BoundNames of BindingIdentifier also occurs in the LexicallyDeclaredNames of FunctionBody. • It is a Syntax Error if the source code matching this production is extended code and any element of the LexicallyDeclaredNames of FormalParameterList also occurs in the VarDeclaredNames of FunctionBody. • It is a Syntax Error if the source code matching this production is extended code and any element of the BoundNames of FormalParameterList also occurs in the LexicallyDeclaredNames of FunctionBody.

# David Herman (14 years ago)

On Jan 16, 2012, at 5:47 PM, Allen Wirfs-Brock wrote:

One of the thing that an "ES6" (or "extended code" as it is currently known in the ES6 draft) mode that is distinct from "strict mode" does is allow us do add additional Early Errors that aren't in ES5 for various existing constructs. It isn't clear that we could continue to have add these errors if we don't have this "mode" distinction as they are generally potentially breaking changes.

The distinction still exists; it's just "code that is syntactically contained within a module."

# Allen Wirfs-Brock (14 years ago)

On Jan 16, 2012, at 5:49 PM, David Herman wrote:

On Jan 16, 2012, at 5:47 PM, Allen Wirfs-Brock wrote:

One of the thing that an "ES6" (or "extended code" as it is currently known in the ES6 draft) mode that is distinct from "strict mode" does is allow us do add additional Early Errors that aren't in ES5 for various existing constructs. It isn't clear that we could continue to have add these errors if we don't have this "mode" distinction as they are generally potentially breaking changes.

The distinction still exists; it's just "code that is syntactically contained within a module."

Except that it isn't clear to me that such a syntactically scoped mode would be compatible with other goals expressed by others on this thread such as, for example, avoiding refactoring pitfalls.

# David Herman (14 years ago)

On Jan 16, 2012, at 6:00 PM, Allen Wirfs-Brock wrote:

On Jan 16, 2012, at 5:49 PM, David Herman wrote:

On Jan 16, 2012, at 5:47 PM, Allen Wirfs-Brock wrote:

One of the thing that an "ES6" (or "extended code" as it is currently known in the ES6 draft) mode that is distinct from "strict mode" does is allow us do add additional Early Errors that aren't in ES5 for various existing constructs. It isn't clear that we could continue to have add these errors if we don't have this "mode" distinction as they are generally potentially breaking changes.

The distinction still exists; it's just "code that is syntactically contained within a module."

Except that it isn't clear to me that such a syntactically scoped mode would be compatible with other goals expressed by others on this thread such as, for example, avoiding refactoring pitfalls.

The only way to avoid refactoring pitfalls entirely is to avoid making any changes in the language altogether. Whether you make changes via a pragma, a MIME type, or a new kind of language construct, you have to deal with refactoring code between the two. Tying it to a syntactic context does mean that the barrier to refactoring is lower, but that's why it's all the more important for us to keep the changes relatively smooth. For example, typeof null is a non-starter in my book.

I believe we should do our best to avoid refactoring pitfalls as much as possible, but I can live with a few (e.g., differing scope rules for block-local functions, arguments aliasing, eval scoping, special cases for destructuring + arguments) in exchange for the drastic improvement to developers' cognitive load, the vastly improved adoption story, and the unity of the spec.

# Andreas Rossberg (14 years ago)

On 16 January 2012 19:35, Brendan Eich <brendan at mozilla.org> wrote:

It's also worth noting that backporting

That is a loaded verb-form ("backporting"). Who says there is a separate older spec or implementation to which to port?

Yes, analyzing interactions requires reading the ES5 spec in non-strict mode. No, the result is not necessarily a port -- it's a patch (if you insist on hacking metaphors).

new ES6 features to classic mode, as has been proposed by several people, clearly works against (3),

I object. 3 is misstated to assume "switching" means all-or-nothing. If ES6 has new features and they are worth using, developers will want to use them piecewise and conveniently. Assuming they will do so at the price of a leading "use version 6" or equivalent pragma is debatable. We shouldn't just assume this conclusion.

OK. But that sounds like a clear departure from the "ES.next/Harmony is based on strict mode" axiom that everybody seemed to have agreed on long ago. Do we have consensus on abandoning that goal? (Thus my description as "backporting".)

and consequently, also against (5/6).

This does not follow. Users transitioning to use new features may be helped, not hindered, by we spec and implementation editors working harder. In particular allowing, e.g., destructuring in non-strict code may be boon to developers, whereas requiring them to maintain explicit opt-in pragmas before doing so simply to convenience spec editors and implementors may be a global loss.

Adding new features to both modes is almost guaranteed to lead to new items to the list of non-strict vs. strict mode differences. That implies new refactoring risks. I don't see a way around that.

Similarly, opting in at smaller scope, as has also been discussed, is a blatant violation of (6) and (7), and works against (3), too.

Let's agree, based on your earlier arguments and also on Dave's point that lexical scope (free variable error analysis) won't work if opt-in is too local.

OK, I'm really glad. :)

So I have plea: let's keep it simple. Start from the simplest of all possible ideas and optimize from there. That is, opting into ES6 through one simple declaration on the top of a program (and nowhere else). Otherwise it's ES5 unchanged.

You are arguing based on "simplest" without considering real-world usability. Too much a-priori system building makes a crashed sky castle, I've seen this over and over. On the web, a-posteriori wins.

On the web, people lose version opt-in pragmas and MIME type versions (Larry Masinter made this point about the latter but it applies to the former). On the web, we have "use strict"; in the middle of concatenations. We have Unicode BOMs in the middle.

So you are saying that there still will be a refactoring hazard. Agreed, but it is tiny compared to the other proposals.

Meanwhile, JS1 grew via ES1-5 and various implementations (SpiderMonkey and Rhino) without any such opt-in, except for two keywords we couldn't reserve (and some incompatible change attempts that failed, vs. goal 8 -- one example: I tried to make == and != strict in JS1.2 to get ES1 to switch, but that broke the web, wherefore === and !==).

Doesn't that kind of make my point? Breaking changes, even small, need opt-in. Strict mode has quite a few of them. Programmers need to be aware of these. I claim that hiding the opt-in locally behind some rather unrelated language feature will not make anybody's life simpler (even if perceived otherwise).

If so, it would be enough to say that a module declaration as the first statement in the program also opts in all of the program.

Why first if (as Dave's proposal in its minimal form did) a later module cannot affect earlier non-strict top level semantics? Again there is no backward compatibility issue. Why, as spec writer or implementor, do you care whether it's

<script>     // non-strict top level code   </script>   <script>     module {...}   </script>

or the concatenation?

As an implementer, I don't necessarily. But as a programmer, I want an easy way to tell the meaning of a local piece of code I'm reading. If the place to opt-in is the beginning of the script, that's the only place I have to look. If not, I potentially have to parse all the preceding program text.

And before somebody brings it up: I do not fear that this will share the fate of strict mode, because there actually is plenty of incentive for using ES6 (especially, if we do not extend classic mode).

Another assumption. There may be more carrots this time, but if the explicit opt-in requirement is too unusable in practice, then ES6 may bounce, or simply have slower adoption that we'd like, ceteris paribus (many variables here, hard to project -- let's avoid arguing based on our favored speculations).

Dave makes this argument, too. I don't know, I may be naive, but I have a very hard time believing that anybody would seriously consider missing out on all the ES6 goodies only because he doesn't like writing one additional, mechanic declaration.

I agree, "use version 6;" looks awful. (Mentioning specific versions seems like a suboptimal idea anyway.) Dave suggested "use module;" (albeit with a somewhat different meaning). Why not just "module;"? Then the rule could be even simpler: the first token decides. :)

# Andreas Rossberg (14 years ago)

On 16 January 2012 23:18, David Herman <dherman at mozilla.com> wrote:

The goal that is missing, and what I believe is the single most important part of my New Year's email, is:

  1. Allow programmers to continue thinking of JS as a single cohesive language.

This is a relative concept rather than an absolute one, but it has real practical consequences on the overall cognitive complexity of the language. If you look at the terminology we've been using in TC39 for years, we either talk about multiple "modes" of JS, or even talk about ES6 as if it's "the new language." I believe this has been a mistake. Now, we've always agreed that ES6 must necessarily subsume ES5. And you might claim that this was just imprecise and casual speech, but I think it betrays a flawed assumption: that ES6 gives us leeway to make clean breaks with the past.

[...]

Instead, we should hew as close as possible to an ideal of One JavaScript, knowing that we'll never be perfect. But -- I believe -- we can get pretty darn close.

Thanks for that, I think we are getting to the core of our disagreement. It is kind of funny, because my goal is exactly the same, but from a different perspective.

With your proposal, you give the current generation of programmers the illusion that ES6 is a simple extension of ES5. But all future programmers, who grow up with ES6 or beyond, will still have to deal with the legacy of ES5 non-strict mode. There will be two modes to worry about forever after (unless we make another breaking change). Refactoring pitfalls will persist.

What I envision makes the transition somewhat more explicit for current programmers (ideally, they need to write at most one extra keyword). But anybody who starts development with ES6+ will never have to care about modes again. For her/him, extended mode is all that's relevant. Refactoring pitfalls only arise during initial version transition.

I claim that on the long run, the latter will converge much closer on the ideal of One JavaScript. With your proposal, you are essentially trading perceived simplicity (which, as an aside, is factual complexity) for current JS programmers for increased complexity for all future JS programmers.

You make a good point with JS code in HTML attributes, though. I don't have a good answer for that, except falling back to a meta tag. For that specific purpose, that might be fine.

# Andreas Rossberg (14 years ago)

On 17 January 2012 03:12, David Herman <dherman at mozilla.com> wrote:

The only way to avoid refactoring pitfalls entirely is to avoid making any changes in the language altogether.

I disagree, see my previous reply. It depends on what class of refactoring pitfalls you are talking about.

I believe we should do our best to avoid refactoring pitfalls as much as possible, but I can live with a few (e.g., differing scope rules for block-local functions, arguments aliasing, eval scoping, special cases for destructuring + arguments) in exchange for the drastic improvement to developers' cognitive load, the vastly improved adoption story, and the unity of the spec.

I'll repeat a point I've tried to make earlier. Having all (or the majority of) new features in both classic and extended mode does not decrease cognitive load, nor unity of the spec. That's an illusion (which, admittedly, may work with developers, but it's cheating on them). Instead, there are more cases everybody has to worry about. Language complexity will be increased, and "unity of the spec" is merely a euphemism for having to spec ugly corner cases that you didn't have to spec otherwise.

Let me put it a different way. One lesson I've learned over years is:

  • context-dependent lexical syntax is a bad idea.
  • context-dependent grammatical syntax is a bad, bad idea.
  • context-dependent static semantics is a bad, bad, bad idea.
  • context-dependent dynamic semantics is a bad, bad, bad, bad idea.
  • context-dependent library semantics is... well, you get the idea...

There already is enough of that in JS that we cannot get rid of. I'd prefer not to combinatorially enlarge that set. And the implicit, scoped opt-in idea is on the 4th level of badness. ;)

And with that, I gonna shut up.

# Herby Vojčík (14 years ago)

Andreas Rossberg wrote:

On 16 January 2012 23:18, David Herman<dherman at mozilla.com> wrote:

The goal that is missing, and what I believe is the single most important part of my New Year's email, is:

  1. Allow programmers to continue thinking of JS as a single cohesive language.

This is a relative concept rather than an absolute one, but it has real practical consequences on the overall cognitive complexity of the language. If you look at the terminology we've been using in TC39 for years, we either talk about multiple "modes" of JS, or even talk about ES6 as if it's "the new language." I believe this has been a mistake. Now, we've always agreed that ES6 must necessarily subsume ES5. And you might claim that this was just imprecise and casual speech, but I think it betrays a flawed assumption: that ES6 gives us leeway to make clean breaks with the past.

[...]

Instead, we should hew as close as possible to an ideal of One JavaScript, knowing that we'll never be perfect. But -- I believe -- we can get pretty darn close.

...

With your proposal, you give the current generation of programmers the illusion that ES6 is a simple extension of ES5. But all future programmers, who grow up with ES6 or beyond, will still have to deal with the legacy of ES5 non-strict mode. There will be two modes to worry about forever after (unless we make another breaking change). Refactoring pitfalls will persist.

What I envision makes the transition somewhat more explicit for current programmers (ideally, they need to write at most one extra keyword). But anybody who starts development with ES6+ will never have to care about modes again. For her/him, extended mode is all that's relevant. Refactoring pitfalls only arise during initial version transition.

I claim that on the long run, the latter will converge much closer on the ideal of One JavaScript. With your proposal, you are essentially trading perceived simplicity (which, as an aside, is factual complexity) for current JS programmers for increased complexity for all future JS programmers.

I agree with this. I think only a few things should be added to ES5 non-strict (destructuring, new object literal, .{...}, <| {...}, maybe something more) and one and only one opt-in to ES6 should be module {...}, eventually "use module;" or similar non-bracing implicit module-wrapper form at the beginning of program (which can be trivially taken care by concat-builders by wrapping such program in module { ... }; it assume the "use module;" form is otherwise silently ignored inside proper module and is syntax error outside it except as a first thing in program). Nothing else (class in ES5 non-strict is syntax error etc.)

This keeps it simple and does not force you to move to ES6 for small useful details, only for more serious new functionality.

# Claus Reinke (14 years ago)

So I'd have to write

use version 6; module {...}

instead of just

module {...}

even though there's no backward compatibility issue? That's just mean! :-|

Why? Assuming that modules work out, they will be in ES7,8,.. as well. So, 'module' isn't sufficient to identify ES6. At best, 'module' identifies ESn (n>5). And that assumes that the feature will remain unchanged

for all ESns to come. I would not want to make that assumption even for 'module', let alone for 'class'.

I like the idea of backwards compatibility, being able to run code for the first PCs on processors design decades later. The more of that you can pull off without cost, the better.

But not being able to throw anything away has a high cost. And I don't think I want to give up on the idea of using ES6 to clean up some dark corners of ES, just because someone doesn't want to have to write 'use ES6'. Client-side JS coders are already used to explicit opt-ins, like 'doctype', to opt in to browser standard mode.

If you think (i.e., hope) that your ES6 code will work without issues in ES7, you can leave out the 'use ES6' and let the engine choose the current default version (if it happens to be an ES5 engine, bad luck). If you want to document what version of ES your code is known to work with, you can use 'use ES6' (the ES7 engines either knows how to handle that, or can give a sensible early error/warning; ES5 engines will give a useful syntax error).

Versioning should be scoped, to reduce concatenation issues, and the features we want put constraints on what level of version scoping is possible/sane. Still, as a 0th approximation, one could start with versioning to the next enclosing block/top-level, then rule out complicated nestings of differently versioned blocks.

Versioning should also be modular: if I want to use 'yield', why do I have to opt into all of ES6? There may be engines that support 'yield' but not yet (or no longer, or not at all) other features of ES6.

Modular/feature-based versioning would also help with your fingers-of-fate problem: you don't want to introduce too many changes so that coders don't get overwhelmed. But if coders can opt in to ES6 features selectively, that is less of an issue.

So I would like to be able to say things like

use yield, let, destructuring; // opt in to specific features

use ES6;    // opt in to a standard collection of features

                  // no explicit opt-in means implicit opt-in to
                  // current default version

use ES5;    // explicitly opt in to an old standard, in case
                  // engines, standards, and defaults move on

Based on Haskell experience again, every feature/opt-in should have its negation/opt-out, as well:

use ES6, !typeof-reform;    // code will mostly work in ES6,
                                    // but isn't ready for this feature

Fortunately, client-side JS coders have a lot of experience with code versioning in the presence of rapidly changing feature sets, and it is accepted best practice to do feature-testing instead of the older practice of user-agent-version-number-testing.

So it isn't as if TC39 would have to introduce any completely foreign ideas about versioning to JS coders - we only need to translate the ideas into a form suitable for language feature versioning.

Do you really think that JS coders who start their codebase with modernizr and the like, to figure out whether the features they want to use are supported on the engine that runs their code, will be scared away by documenting the language features their code relies on?

My non-speculative argument rests on experience on the web. Version marks and other separators are mangled and lost all the time.

The point is that explicit version marks can be pointed to, and checked against the code ("your car and airplane should have seatbelts - if not, change the dealer or airline"; "yes, even well preserved old-timers are now supposed to have seatbelts"; "oh, there it is, it just got mangled into that gap").

Implicit version marks use defaults, which change over time or with context ("hmm, no-one else here seems to have seat-belts, so that must be okay?"). At best, you'll have to remember what the current defaults are and what the intended defaults were. At worst, you'll be surprised.

Claus clausreinke.github.com

I believe we should offer well-defined <script type=...;version=...> opt-in for script content hiding from old UAs, and in addition belt-and-braces in-script-content version opt-in at a minimum, via some pragma such as |use version 6;|.

The latter would also work in eval-ed code.

# Brendan Eich (14 years ago)

Andreas Rossberg <mailto:rossberg at google.com> January 17, 2012 4:49 AM On 16 January 2012 19:35, Brendan Eich<brendan at mozilla.org> wrote:

new ES6 features to classic mode, as has been proposed by several people, clearly works against (3), I object. 3 is misstated to assume "switching" means all-or-nothing. If ES6 has new features and they are worth using, developers will want to use them piecewise and conveniently. Assuming they will do so at the price of a leading "use version 6" or equivalent pragma is debatable. We shouldn't just assume this conclusion.

OK. But that sounds like a clear departure from the "ES.next/Harmony is based on strict mode" axiom that everybody seemed to have agreed on long ago. Do we have consensus on abandoning that goal? (Thus my description as "backporting".)

It's not quite the departure you might think. E.g. destructuring formal parameters piggy-backs on strict mode logic to forbid duplicates anywhere in the parameter list. More below.

and consequently, also against (5/6). This does not follow. Users transitioning to use new features may be helped, not hindered, by we spec and implementation editors working harder. In particular allowing, e.g., destructuring in non-strict code may be boon to developers, whereas requiring them to maintain explicit opt-in pragmas before doing so simply to convenience spec editors and implementors may be a global loss.

Adding new features to both modes is almost guaranteed to lead to new items to the list of non-strict vs. strict mode differences. That implies new refactoring risks. I don't see a way around that.

I do. We have strict mode changes, both early errors and runtime changes in meaning. For the early errors, we piggy-back to make new syntax in non-strict code trigger the same error, as with the destructuring parameters example above. For runtime shifts in meaning, we can still piggy-back if the change is "enclosed" by the new syntax.

What do I mean by enclosed? Consider destructuring parameters. Should they trigger strict arguments object semantics, a runtime shift? No, the destructuring pattern in one formal parameter does not enclose the whole parameter list, or body uses of arguments. So what should arguments semantics be? Non-strict.

Why isn't this a problem? Because destructuring parameters have no named formal for the whole object passed as the actual and destructured by the pattern. There's no need to alias, and we do not want deep-aliasing, ever (we = users, implementors).

Is this split between enabling early strict-like errors for duplicates, but not triggering strict arguments, a refactoring risk? I don't know. I think we should look at it. It could be a risk, certainly -- but it may be tiny.

Similarly, opting in at smaller scope, as has also been discussed, is a blatant violation of (6) and (7), and works against (3), too. Let's agree, based on your earlier arguments and also on Dave's point that lexical scope (free variable error analysis) won't work if opt-in is too local.

OK, I'm really glad. :)

See, toldya es-discuss is not scary ;-).

Meanwhile, JS1 grew via ES1-5 and various implementations (SpiderMonkey and Rhino) without any such opt-in, except for two keywords we couldn't reserve (and some incompatible change attempts that failed, vs. goal 8 -- one example: I tried to make == and != strict in JS1.2 to get ES1 to switch, but that broke the web, wherefore === and !==).

Doesn't that kind of make my point? Breaking changes, even small, need opt-in.

No, JS1.2 with its version (via the old language="JavaScript1.2" attribute, remember that) failed. ES1 changed JS de-facto standards without opt-in. Same for ES2 and 3. We supported versioning via language= and later type= but it was hardly used outside of Mozilla-specific JS.

Developers do not want version opt-in, certainly not as a requirement. One JS.

Have to stop here, out of time. Please feel free to reply to anything, I hope I didn't miss something later in your message that invalidates what I wrote in this message!