ES6 doesn't need opt-in

Luke Hoban lukeh at microsoft.com
Thu Jan 5 22:38:52 PST 2012


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 <http://wiki.ecmascript.org/doku.php?id=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.

--
    Cheers,
    --MarkM

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20120106/4a824e6a/attachment-0001.html>


More information about the es-discuss mailing list