Global lexical tier
Personally I am a fan of Chrome's CURRENT solution:
Uncaught SyntaxError: Block-scoped declarations (let, const, function,
class) not yet supported outside strict mode
- Matthew Robb
I think a better solution would have been to have all top-level let/const/class variables just be local to that specific Program. That's how I originally implemented it in JavaScriptCore, and now I'm going back and "fixing" it to be as it is defined in the Spec. (That said, I agree that allowing these to be global properties is better than how it's currently defined.)
Saam
Note that this behavior in v8/Chrome is only about sloppy mode. In strict mode, v8 implements the ES6 spec (and we'll likely have support for lexical declarations in sloppy mode soon).
Though I personally agree that the top-level lexical scoping in ES6 seems unfortunate.
We are in rapid-release hell/heaven.
This means errata can be issued, and engines can implement the better resolution for those errata, compared to what the last major-version de jure spec mandated.
Why not?
I don't see how strict/sloppy mode effects the behavior of top-level lexical declarations. Does the behavior depend on strict mode?
I agree with Brendan: I vote for changing this if we can.
Saam
We should have just made toplevel let/const/class create global properties, like var. This is how it was proposed originally
Can you provide (or link to) a more specific proposal?
I don't see how strict/sloppy mode effects the behavior of top-level lexical declarations. Does the behavior depend on strict mode?
No, V8 just currently disallows the use of let/const/class outside of strict mode.
On Aug 31, 2015, at 7:11 PM, Brendan Eich wrote:
We are in rapid-release hell/heaven.
This means errata can be issued, and engines can implement the better resolution for those errata, compared to what the last major-version de jure spec mandated.
Why not?
/be
The possible semantics of global lexical declarations was extensively explored by TC39 before we make the final design decisions for ES6. The scoping of global lexical declarations is something we spent many hours discussing over a span of several years, including:
rwaldron/tc39-notes/blob/master/es6/2012-07/july-25.md#scoping-rules-for-global-lexical-declaration, rwaldron/tc39-notes/blob/master/es6/2012-09/sept-19.md#global-scope-revisit, rwaldron/tc39-notes/blob/master/es6/2013-11/nov-21.md#46-the-global-scope-contour, rwaldron/tc39-notes/blob/master/es6/2014-11/nov-18.md#45-global-let-shadowing-of-global-object-properties
plus various es-discuss threads: www.google.com/?gws_rd=ssl#q=site:http:%2F%2Fesdiscuss.org+global+let
There were many issues and alternatives to consider, and we did within TC39. All of the possible approaches had downsides, but we achieved consensus to proceed with what is now in the spec.
Both the alternative suggest by Jason and the one suggest by Saam were considered and rejected in these discussions. It's easy to imagine a different semantics. It's harder to demonstrate that it is a "better semantics" and to get agreement on that. If you want to change the ES6 spec you need to explore all of the issues that came in the the documented TC39 discussions and explain why, in light of all those other issues, the ES6 design isn't the best comprise solution to problem with no perfect or easy solution.
Regarding, global declarations in a REPL. A REPL is a extra-lingual features. In it not covered by the language specification. A REPL implementation is certainly free to define it's own rules for dealing with the issues that Jason mentioned. For example, a REPL could allow redefinition of global lexical declaration or it could allow lexical bindings created using the REPL to be deletable.
On Aug 31, 2015, at 7:11 PM, Brendan Eich wrote:
We are in rapid-release hell/heaven.
This means errata can be issued, and engines can implement the better resolution for those errata, compared to what the last major-version de jure spec mandated.
Yes. Obviously the bar is very, very high.
I'd like to hear from other implementers on whether this is worth considering at all. I think the change would be backward-compatible except for code that depends on redeclaration errors, or on variables not being reflected on the window object.
On Tue, Sep 1, 2015 at 12:03 AM, Allen Wirfs-Brock wrote:
Both the alternative suggest by Jason and the one suggest by Saam were considered and rejected in these discussions. It's easy to imagine a different semantics. It's harder to demonstrate that it is a "better semantics" and to get agreement on that. [...]
This is a very thorough and fair response, Allen, thanks.
Regarding, global declarations in a REPL. A REPL is a extra-lingual features. In it not covered by the language specification. A REPL implementation is certainly free to define it's own rules [...]
Well, yes, but at some cost to developers, who use the REPL to inquire into the language's behavior, right? The more modes we have, and the more different the REPL mode is, the more often the REPL will mislead them.
(...to digress a bit, the SpiderMonkey JS shell currently defaults to a nonstandard mode, for unrelated reasons. And it does mislead people. I want to change it: bugzilla.mozilla.org/show_bug.cgi?id=1192329)
In any case, we're agreed that an occasional bad REPL interaction would be no big deal on its own.
The possible semantics of global lexical declarations was extensively explored by TC39 before we make the final design decisions for ES6. The scoping of global lexical declarations is something we spent many hours discussing over a span of several years, including:
To be fair, some problems in design only become apparent after implementation and use.
For example, in the case of global lexical scope, implementing and testing the details of the current ECMAScript specification led Saam to conclude — and led me to agree — that the behavior was strange, and hard to reason about.
I hope you aren’t saying that ECMAScript decisions should become permanent and irreversible prior to any implementation or adoption effort. In other web standards, implementation and adoption guide specification — to the benefit of the specification.
Geoff
On Sep 1, 2015, at 11:04 AM, Geoffrey Garen wrote:
The possible semantics of global lexical declarations was extensively explored by TC39 before we make the final design decisions for ES6. The scoping of global lexical declarations is something we spent many hours discussing over a span of several years, including:
To be fair, some problems in design only become apparent after implementation and use.
For example, in the case of global lexical scope, implementing and testing the details of the current ECMAScript specification led Saam to conclude — and led me to agree — that the behavior was strange, and hard to reason about.
I hope you aren’t saying that ECMAScript decisions should become permanent and irreversible prior to any implementation or adoption effort. In other web standards, implementation and adoption guide specification — to the benefit of the specification.
Geoff
What I'm saying is that this is more about "requirements" than implementations. There was a complex (and sometimes conflicting) set of requirements for the semantics of the new global declarations. The final design that TC39 accepted threaded the needle among those requirements. We were aware that some people (including some TC39 members) would probably find some aspects of the design might be strange or quirky. However, that tends to be the nature of solutions when dealing with complex requirements.
Suggestions to change the design needs to address how each of those requirements can still met or why some of the requirements are not longer relevant. Evidence that the design is impossible to implement would be important news. But I don't think anybody has yet made that assertion. Observations that it is strange is less useful. So are alternative designs that are presented without consideration of all of the initial requirements.
What were the requirements for the global lexical scope? Is it written somewhere I can read? I came up empty after a quick search of esdiscuss.org.
I think the whole point of ES6 lexical scoping is to limit scope to something you can read in a given file (or script, or block, or eval, or unit of JS code). Except, this breaks down for global top-level let/const/class. I argue that if you want these variables to be visible across JS programs, you should be using modules. The current spec makes this somewhat better than "var"s because these variables are no longer properties on the global object, but it punted and said that there is some magical lexical scope that wraps all programs. I think it's more in the spirit of lexical scoping to have these variables be limited to the program they're defined in (as if the entire program were wrapped in {}). And it's more in the spirit of ES6 to share code across programs using modules.
I think it's not helpful for you to threaten that our opinions are less useful than a proof of impossibility when we are people who are both interested in the JavaScript language and interested in implementing it well. I've spent > 8 hours today implementing the "global lexical tier", so I think it's helpful to categorize my opinion as being slightly greater than "less useful than a proof that the spec is impossible to implement". Sometimes the best ideas are those that are negligent of "requirements".
Saam
On Tue, Sep 1, 2015 at 10:41 PM SaamBarati1 <saambarati1 at gmail.com> wrote:
Hi Allen,
What were the requirements for the global lexical scope? Is it written somewhere I can read? I came up empty after a quick search of esdiscuss.org.
I think the whole point of ES6 lexical scoping is to limit scope to something you can read in a given file (or script, or block, or eval, or unit of JS code). Except, this breaks down for global top-level let/const/class. I argue that if you want these variables to be visible across JS programs, you should be using modules. The current spec makes this somewhat better than "var"s because these variables are no longer properties on the global object, but it punted and said that there is some magical lexical scope that wraps all programs. I think it's more in the spirit of lexical scoping to have these variables be limited to the program they're defined in (as if the entire program were wrapped in {}). And it's more in the spirit of ES6 to share code across programs using modules.
I think it's not helpful for you to threaten that our opinions are less useful than a proof of impossibility when we are people who are both interested in the JavaScript language and interested in implementing it well. I've spent > 8 hours today implementing the "global lexical tier", so I think it's helpful to categorize my opinion as being slightly greater than "less useful than a proof that the spec is impossible to implement". Sometimes the best ideas are those that are negligent of "requirements".
Allen already provided links to discussions of record that lead to the design in ES6. Those discussions capture the requirements and supporting rationale. Here they are again:
rwaldron/tc39-notes/blob/master/es6/2012-07/july-25.md#scoping-rules-for-global-lexical-declaration
rwaldron/tc39-notes/blob/master/es6/2012-09/sept-19.md#global-scope-revisit
rwaldron/tc39-notes/blob/master/es6/2013-11/nov-21.md#46-the-global-scope-contour
plus various es-discuss threads: www.google.com/?gws_rd=ssl#q=site:http:%2F%2Fesdiscuss.org+global+let, www.google.com/?gws_rd=ssl#q=site:http://esdiscuss.org+global+let
Thanks. Reading now.
I'm clearly bad at email :/
saam barati wrote:
Thanks. Reading now.
I'm clearly bad at email :/
Naw, this stuff is always harder to find than it should be.
I was there, I just re-read and re-remembered. I do not agree with Allen that some tiny needle was uniquely threaded. Rather, an aesthetic preference for the new ES6 binding forms to have a lexical contour of their own when used at top level prevailed. This leads to problems, not all of which were known at the time -- but some problems were noted.
The REPL problem, where let z=z; makes a poison pill, could be coped with by ad-hoc REPL deviations from the spec -- at some cost. Let's set it aside.
The one-time change to a reference, from global object to lexical shadowing binding, is a serious flaw. Yes, it could happen due to explicit scope nesting, but the global scope is apparently uniform. There's no explicit delimiter.
The implementors seem to be rebelling but I'm not trying to stir up trouble. It would help if V8 did support let, etc. in sloppy mode. Then we might see open rebellion among two or more implementors.
When it comes to aesthetics vs. implementability and usability, we have to throw aesthetics under the bus. This is JavaScript, after all! :-P Ok, seriously, we did not actually make anything prettier. The top level is hopeless. All we did was leave a couple of hazards for implementors and users in ES6.
Making the new binding forms create global properties (or throw trying), as I implemented long ago for let in ES4 in SpiderMonkey, is ugly, but it does not introduce any net-new hazards.
On 3 September 2015 at 01:58, Brendan Eich <brendan at mozilla.org> wrote:
I was there, I just re-read and re-remembered. I do not agree with Allen that some tiny needle was uniquely threaded. Rather, an aesthetic preference for the new ES6 binding forms to have a lexical contour of their own when used at top level prevailed. This leads to problems, not all of which were known at the time -- but some problems were noted.
The REPL problem, where let z=z; makes a poison pill, could be coped with by ad-hoc REPL deviations from the spec -- at some cost. Let's set it aside.
The one-time change to a reference, from global object to lexical shadowing binding, is a serious flaw. Yes, it could happen due to explicit scope nesting, but the global scope is apparently uniform. There's no explicit delimiter.
I still maintain that a tower-of-nested-scopes model would have been cleaner AND would have avoided both the shadowing issue and the REPL restriction. A mutable scope that gets extended under your feet is terrible, lexical or not.
I also maintain that putting lexical bindings onto the global object is not an option. It is incompatible with having a TDZ, unless one wants to introduce TDZs for properties into the object model, which I doubt anybody wants. Or unless one makes toplevel binding semantics completely different (and insane), which I also hope nobody wants (though I'm not so sure).
The implementors seem to be rebelling but I'm not trying to stir up
trouble. It would help if V8 did support let, etc. in sloppy mode. Then we might see open rebellion among two or more implementors.
AFAICS this issue is completely orthogonal to sloppy-vs-script, and there are no new effects specific to sloppy mode. V8 already fully implements the necessary semantics. It wasn't pretty, but there are uglier things in ES6.
The main thing holding back sloppy let in V8 right now is the parsing nonsense and extra look-ahead required, which turns out to be a major pain for us (and FWIW, slows down the V8 parser by a couple of percent with little hope for recovery :( ).
Andreas Rossberg wrote:
On 3 September 2015 at 01:58, Brendan Eich <brendan at mozilla.org <mailto:brendan at mozilla.org>> wrote:
I was there, I just re-read and re-remembered. I do not agree with Allen that some tiny needle was uniquely threaded. Rather, an aesthetic preference for the new ES6 binding forms to have a lexical contour of their own when used at top level prevailed. This leads to problems, not all of which were known at the time -- but some problems were noted. The REPL problem, where let z=z; makes a poison pill, could be coped with by ad-hoc REPL deviations from the spec -- at some cost. Let's set it aside. The one-time change to a reference, from global object to lexical shadowing binding, is a serious flaw. Yes, it could happen due to explicit scope nesting, but the global scope is apparently uniform. There's no explicit delimiter.
I still maintain that a tower-of-nested-scopes model would have been cleaner AND would have avoided both the shadowing issue and the REPL restriction. A mutable scope that gets extended under your feet is terrible, lexical or not.
I don't remember you overcoming the counterarguments about async scripts and event handlers in async-generated/written markup twisting the nested scopes unexpectedly.
I also maintain that putting lexical bindings onto the global object is not an option. It is incompatible with having a TDZ, unless one wants to introduce TDZs for properties into the object model,
Nope!
which I doubt anybody wants. Or unless one makes toplevel binding semantics completely different (and insane), which I also hope nobody wants (though I'm not so sure).
Something has to give. This seems least bad.
The implementors seem to be rebelling but I'm not trying to stir up trouble. It would help if V8 did support let, etc. in sloppy mode. Then we might see open rebellion among two or more implementors.
AFAICS this issue is completely orthogonal to sloppy-vs-script, and there are no new effects specific to sloppy mode. V8 already fully implements the necessary semantics. It wasn't pretty, but there are uglier things in ES6.
There's still user not implementor feedback to be gained by V8 supporting top-level let/const/class in sloppy mode per spec, but fair enough.
The main thing holding back sloppy let in V8 right now is the parsing nonsense and extra look-ahead required, which turns out to be a major pain for us (and FWIW, slows down the V8 parser by a couple of percent with little hope for recovery :( ).
I thought we resolved this (non-simple parameter list in function makes "use strict"; directive prologue an early error). What's left?
On Sep 2, 2015, at 4:58 PM, Brendan Eich wrote:
saam barati wrote:
Thanks. Reading now.
I'm clearly bad at email :/
Naw, this stuff is always harder to find than it should be.
I was there, I just re-read and re-remembered. I do not agree with Allen that some tiny needle was uniquely threaded. Rather, an aesthetic preference for the new ES6 binding forms to have a lexical contour of their own when used at top level prevailed. This leads to problems, not all of which were known at the time -- but some problems were noted.
I didn't mean to imply that it was the only threading of the requirement needles, but it was the one we could reach consensus on. It isn't even my favorite ( I would of preferred something similar to Saam's suggestion) but consensus on something was necessary in order to have publish a standard.
The REPL problem, where let z=z; makes a poison pill, could be coped with by ad-hoc REPL deviations from the spec -- at some cost. Let's set it aside.
The one-time change to a reference, from global object to lexical shadowing binding, is a serious flaw. Yes, it could happen due to explicit scope nesting, but the global scope is apparently uniform. There's no explicit delimiter.
The implementors seem to be rebelling but I'm not trying to stir up trouble. It would help if V8 did support let, etc. in sloppy mode. Then we might see open rebellion among two or more implementors.
When it comes to aesthetics vs. implementability and usability, we have to throw aesthetics under the bus. This is JavaScript, after all! :-P Ok, seriously, we did not actually make anything prettier. The top level is hopeless. All we did was leave a couple of hazards for implementors and users in ES6.
Making the new binding forms create global properties (or throw trying), as I implemented long ago for let in ES4 in SpiderMonkey, is ugly, but it does not introduce any net-new hazards.
But it introduces many inconsistencies between the semantics of let/const/class at the global level and their handling within nested scopes. Also, it perpetuates the the pollution of the windows object with program state, the avoidance of which was one of the requirements that was being promoted.
The way I see it, when none of the alternatives are good ones, it is TC39's job to choose (as long as the choice is implementable). In such cases implementations should just follow the spec, unless there is a truly new alternative that T39 didn't consider or other new information. It really isn't very usefully to revisit a difficult decision without any new information to add to the discussion.
On 3 September 2015 at 03:50, Brendan Eich <brendan at mozilla.org> wrote:
Andreas Rossberg wrote:
On 3 September 2015 at 01:58, Brendan Eich <brendan at mozilla.org <mailto: brendan at mozilla.org>> wrote:
I was there, I just re-read and re-remembered. I do not agree with Allen that some tiny needle was uniquely threaded. Rather, an aesthetic preference for the new ES6 binding forms to have a lexical contour of their own when used at top level prevailed. This leads to problems, not all of which were known at the time -- but some problems were noted. The REPL problem, where let z=z; makes a poison pill, could be coped with by ad-hoc REPL deviations from the spec -- at some cost. Let's set it aside. The one-time change to a reference, from global object to lexical shadowing binding, is a serious flaw. Yes, it could happen due to explicit scope nesting, but the global scope is apparently uniform. There's no explicit delimiter.
I still maintain that a tower-of-nested-scopes model would have been cleaner AND would have avoided both the shadowing issue and the REPL restriction. A mutable scope that gets extended under your feet is terrible, lexical or not.
I don't remember you overcoming the counterarguments about async scripts and event handlers in async-generated/written markup twisting the nested scopes unexpectedly.
The only cases where the scope order would make an observable difference
are ones that produce conflicts right now. So you'd only allow a few more
programs -- somewhat ill-behaved (non-deterministic) ones, but no more
ill-behaved than those that you can write with var
today (or could with
let
if it was a property on the global object! -- non-deterministic
shadowing is still a less drastic effect than non-deterministic
overwriting).
So from my perspective, there is nothing to overcome, at least not relative to other alternatives. :) In particular, this one:
Or unless one makes toplevel binding semantics completely different (and
insane), which I also hope nobody wants (though I'm not so sure).
Something has to give. This seems least bad.
I disagree. In particular, since this solution is even less well-behaved, see above.
The main thing holding back sloppy let in V8 right now is the parsing
nonsense and extra look-ahead required, which turns out to be a major pain for us (and FWIW, slows down the V8 parser by a couple of percent with little hope for recovery :( ).
I thought we resolved this (non-simple parameter list in function makes "use strict"; directive prologue an early error). What's left?
It's unrelated. The grammar is LL(2) regarding sloppy let
, so the scanner
needs the ability to do one extra token of look-ahead to let the
recursive-decent parser deal with it (unless, perhaps, you completely
transform all the grammar; not sure if that would be possible). Enabling
that creates a slight performance bottleneck. Plus, look-ahead in a JS
scanner is very brittle, given the lexical ambiguities and context
dependencies around regexps.
After reading through past threads and meeting notes, I've changed my opinion. I think the current spec is OK and meets the real needs of programs today. I've ran into real programs that break without the global lexical tier while implementing this inside JavaScriptCore. While I don't think global lexical tier is aesthetically pleasing, I think there is a practicality to it. I think we've also hurt ourselves here with to considering alternate implementations by having some form of "const" being visible across multiple JS programs for a while now.
Saam
saam barati wrote:
I think we've also hurt ourselves here with to considering alternate implementations by having some form of "const" being visible across multiple JS programs for a while now.
This property of top-level const being visible across mutliple programs (script elements) holds under the current ES6 spec (global lexical tier). Were you remarking on how const under the counter-proposal Jason brought up would bind a global object property? Otherwise the multiple-programs-see-one-program's-const situation doesn't change from either proposal to the other.
Sorry for the confusion, I was talking about my stated preference that all lexical variables should be local to the program they're defined in.
As you probably read (agreed, notes are strung out and not well-indexed), we considered that too. It fell to the objection that users certainly want const and class declarations from one script to be usable in subsequent scripts. For let, we didn't want to break symmetry with const and class at top level (if you want per-script let, use a block around the whole script body).
Andreas Rossberg wrote:
On 3 September 2015 at 03:50, Brendan Eich <brendan at mozilla.org <mailto:brendan at mozilla.org>> wrote:
I don't remember you overcoming the counterarguments about async scripts and event handlers in async-generated/written markup twisting the nested scopes unexpectedly.
The only cases where the scope order would make an observable difference are ones that produce conflicts right now. So you'd only allow a few more programs -- somewhat ill-behaved (non-deterministic) ones, but no more ill-behaved than those that you can write with
var
today (or could withlet
if it was a property on the global object! -- non-deterministic shadowing is still a less drastic effect than non-deterministic overwriting).
The problem is the cognitive model and load. Nothing helps non-deterministic loading of scripts with global effects, but making every. last. script! nest a fresh lexical scope means "now you have two problems".
People should keep eyes on the prize. You want a lexical-only top-level, use modules. Real-world JS hackers already are, server- and client-side.
Let script be what it is, a means of global object coordination (object detection, primarily) as well as global code loading. It's not going away any time soon, but it need not be over-complicated to serve the "lexical all the way up" use-case poorly compared to modules.
I thought we resolved this (non-simple parameter list in function makes "use strict"; directive prologue an early error). What's left?
It's unrelated. The grammar is LL(2) regarding sloppy
let
, so the scanner needs the ability to do one extra token of look-ahead to let the recursive-decent parser deal with it (unless, perhaps, you completely transform all the grammar; not sure if that would be possible). Enabling that creates a slight performance bottleneck. Plus, look-ahead in a JS scanner is very brittle, given the lexical ambiguities and context dependencies around regexps.
I'm interested in what other implementors have to say.
Allen Wirfs-Brock wrote:
On Sep 2, 2015, at 4:58 PM, Brendan Eich wrote:
saam barati wrote:
Thanks. Reading now.
I'm clearly bad at email :/
Naw, this stuff is always harder to find than it should be.
I was there, I just re-read and re-remembered. I do not agree with Allen that some tiny needle was uniquely threaded. Rather, an aesthetic preference for the new ES6 binding forms to have a lexical contour of their own when used at top level prevailed. This leads to problems, not all of which were known at the time -- but some problems were noted.
I didn't mean to imply that it was the only threading of the requirement needles, but it was the one we could reach consensus on. It isn't even my favorite ( I would of preferred something similar to Saam's suggestion)
Global script is global, though. I don't see how you can have
<script> class Widget {...} </script> ... <script> let w = new Widget(); ... </script>
fail for want of an extra step to export Widget from the first script and import it into the second. Modules, sure, but scripts aren't modules.
Anyway, we indeed seek consensus and give up our favorites, saving them for told-you-so moments later ;-).
but consensus on something was necessary in order to have publish a standard.
Yep. But this is es-discuss, so fair to discuss (and rehash every year :-P), and what's more: implementor feedback is way overdue. That's what Jason is bringing to us, we need to attend to it.
ES6 took a lot of risk running ahead of any implementor. Last time, we promise, eh?
On Sep 3, 2015, at 2:30 PM, Brendan Eich wrote:
\
Global script is global, though. I don't see how you can have
<script> class Widget {...} </script> ... <script> let w = new Widget(); ... </script>
fail for want of an extra step to export Widget from the first script and import it into the second. Modules, sure, but scripts aren't modules.
Yes, that was the objection. But a (reasonable?) workaround might have been:
<script>
var Widget = class {...} </script>
Anyway, we indeed seek consensus and give up our favorites, saving them for told-you-so moments later ;-).
but consensus on something was necessary in order to have publish a standard.
Yep. But this is es-discuss, so fair to discuss (and rehash every year :-P), and what's more: implementor feedback is way overdue. That's what Jason is bringing to us, we need to attend to it.
ES6 took a lot of risk running ahead of any implementor. Last time, we promise, eh?
Fingers crossed. Implementors have to step-up and take the risk of implementing "big" features before final standardization. We seem to be making some progress there with async functions and simd.
Alen
On 3 September 2015 at 20:57, Brendan Eich <brendan at mozilla.org> wrote:
Andreas Rossberg wrote:
On 3 September 2015 at 03:50, Brendan Eich <brendan at mozilla.org <mailto: brendan at mozilla.org>> wrote:
I don't remember you overcoming the counterarguments about async scripts and event handlers in async-generated/written markup twisting the nested scopes unexpectedly.
The only cases where the scope order would make an observable difference are ones that produce conflicts right now. So you'd only allow a few more programs -- somewhat ill-behaved (non-deterministic) ones, but no more ill-behaved than those that you can write with
var
today (or could withlet
if it was a property on the global object! -- non-deterministic shadowing is still a less drastic effect than non-deterministic overwriting).The problem is the cognitive model and load. Nothing helps non-deterministic loading of scripts with global effects, but making every. last. script! nest a fresh lexical scope means "now you have two problems".
What I mean is that in a model with mutable scope (or worse, the global object) declarations themselves become global effects. Later conflicts can affect all code that has been loaded before, e.g. by smashing over an existing binding.
Andreas Rossberg wrote:
What I mean is that in a model with mutable scope (or worse, the global object) declarations themselves become global effects. Later conflicts can affect all code that has been loaded before, e.g. by smashing over an existing binding.
I get it, but this is just "the way JS works" for var and function declarations at top level. For example, people have figured out how to use var for object detection without violating strict mode by creating a global variable by assignment.
Adding let/const/class at top level either:
- Adds a "second way JS works" (either your favored counter-proposal, the ML-like nested scopes; or the one Saam favored that Allen likes, where each script has an implicit {} around its body as far as those declarations -- but not function -- are concerned; or something else).
or:
- Works by analogy to var (and doesn't bother with TDZ and other peculiarities of bindings in ES6).
My point is (2) doesn't add to the top level model-set, ignoring common complexities of const and class. It just equates let and var. This has a cost too, I agree, but there's no cost-free solution.
Perhaps Allen's point about
<script> var Widget = class {...} </script>
is better from a certain point of view (Crockford advocates var f = function(){} over function f(){}, IIRC, in JS:TGP), but I would bet lots of real money that ES6 users would be bummed to learn that they cannot declare classes (or consts) in one script and use them in the next.
Your nesting counter-proposal allows that, cleverly, but at the cost of another model. The only proposal that doesn't require thinking in a new "second" way for the common cases of class, let without TDZ actually firing, const without TDZ or non-initialiser assignemnt, is the one Jason favored in the o.p.
Can we talk about the global lexical tier?
This was a mistake, a real blunder. We all should have known better. An extensible intermediate scope implies dynamic scoping. The referent of an identifier can change only once, but it can change. It's like an implicit
with
block around all code.This pattern for declaring variable in multiple scripts won't work with let/const:
There's no workaround except "keep using var".
It's now possible to get a binding permanently wedged, which is bad for a REPL:
For a single name to have two global bindings, both mutable, is astonishing.
All of this was unnecessary. What's the benefit to users? We ran roughshod over existing practice, invariants, and expectations in order to obtain a kind of self-consistency for
let
that users don't expect or even care about.We should have just made toplevel let/const/class create global properties, like var. This is how it was proposed originally and how Babel implements it today. For SpiderMonkey, switching to the worse, less user-friendly way without regressing performance is time-consuming.
We knew all this. We didn't evaluate it correctly. What we particularly missed is that we already had modules as the way forward to a nice toplevel lexical tier, and weird half measures for scripts were pointless.