barrier dimension, default dimension

Brendan Eich brendan at mozilla.com
Fri Dec 28 12:45:28 PST 2012


Just to be super-clear, since citing in mail (compose and read-time) is 
hard: everything after "Here's the citation again (...):" is cited from 
the July 2011 meeting notes followup post I made in August 2011.

/be

Brendan Eich wrote:
> Argh, why must mailman archive + copy/paste result in unreadably long 
> lines. Here's the citation again ( from 
> https://mail.mozilla.org/pipermail/es-discuss/2011-August/016188.html):
>
>
> 178 nor Normal All allen at wirfs-brock.com CONF --- Must settle 
> scoping details for block-scoped bindings
>
> Much discussion here. The issue is whether let and const bindings 
> hoist to block top, or start a new implicit scope (the let* or, let's 
> call it, C++ rule). The prior work was nicely diagrammed by Waldemar in:
>
> https://mail.mozilla.org/pipermail/es-discuss/2008-October/007807.html
>
> Quoting from Waldemar's message (note the future-proofing for guards):
>
> --- begin quote ---
>
> There are four ways to do this:
> A1. Lexical dead zone.  References textually prior to a definition in 
> the same block are an error.
> A2. Lexical window.  References textually prior to a definition in the 
> same block go to outer scope.
> B1. Temporal dead zone.  References temporally prior to a definition 
> in the same block are an error.
> B2. Temporal window.  References temporally prior to a definition in 
> the same block go to outer scope.
>
> Let's take a look at an example:
>
> let x = "outer";
> function g() {return "outer"}
>
> {
>   g();
>   function f() { ... x ... g ... g() ... }
>   f();
>   var t = some_runtime_type;
>   const x:t = "inner";
>   function g() { ... x ... }
>   g();
>   f();
> }
>
> B2 is bad because then the x inside g would sometimes refer to "outer" 
> and sometimes to "inner".
>
> A1 and A2 introduce extra complexity but doesn't solve the problem.  
> You'd need to come up with a value for x to use in the very first call 
> to g().  Furthermore, for A2 whether the window occurred or not would 
> also depend on whether something was a function or not; users would be 
> surprised that x shows through the window inside f but g doesn't.
>
> That leaves B1, which matches the semantic model (we need to avoid 
> referencing variables before we know their types and before we know 
> the values of constants).
>
> --- end quote ---
>
> In the September 2010 meeting, however, we took a wrong turn (my fault 
> for suggesting it, but in my defense, just about everyone did prefer 
> it -- we all dislike hoisting!) away from hoisted let and const 
> bindings, seemingly achieving consensus for the C++ rule.
>
> Allen, it turned out, did not agree, and he was right. Mixing 
> non-hoisting (the C++ rule) with hoisting (function in block must 
> hoist, for mutual recursion "letrec" use-cases and to match how 
> function declarations at body/program level hoist) does not work. In 
> the example above, g's use of x either refers to an outer x for the 
> first call to g() in the block, but not the second in the block (and 
> various for the indirect call via f()) -- dynamic scope! -- or else 
> the uses before |const x|'s C++-style implicit scope has opened must 
> be errors (early or not), which is indistinguishable from hoisting.
>
> So at last week's meeting, we finally agreed to the earlier rules: all 
> block-scoped bindings hoist to top of block, with a temporal dead zone 
> for use of let and const before *iniitalization*.
>
> The initialization point is also important. Some folks wondered if we 
> could not preserve var's relative simplicity: var x = 42; is really 
> var x; x = 42, and then the var hoists (this makes for insanity within 
> 'with', which recurs with 'let' in block vs. 'var' of same name in 
> inner block -- IIRC we agreed to make such vars that hoist past 
> same-named let bindings be early errors).
>
> With var, the initialization is just an assignment expression. A name 
> use before that assignment expression has been evaluated results in 
> the default undefined value of the var, assuming it was fresh. There 
> is no read and write barrier requirement, as there is (in general, due 
> to closures) for the temporal dead zone semantics.
>
> But if we try to treat let like var, then let and const diverge. We 
> cannot treat const like var and allow any assignment as 
> "initialization", and we must forbid assignments to const bindings -- 
> only the mandatory initializer in the declaration can initialize. 
> Trying to allow the "first assignment to a hoisted const" to win 
> quickly leads to two or more values for a single const binding:
>
> {
>   x = 12;
>   if (y) return x;
>   const x = 3;
>   ...
> }
>
> The situation with let is constrained even ignoring const. Suppose we 
> treat let like var, but hoisted to block top instead of body/program 
> top, with use before set reading undefined, or in an alternative model 
> that differs from var per temporal dead zone, throwing. So:
>
> {
>   print(x);
>   x = 12;
>   let x;
> }
>
> would result in either print being called with undefined or an error 
> on the use of x before it was set by the assignment 
> expression-statement -- those are the two choices given hoisting.
>
> But then:
>
> {
>   x = 12;
>   print(x);
>   let x;
> }
>
> would result in either 12 being printed or an error being thrown 
> assigning to x before its declaration was evaluated.
>
> Any mixture of error with non-error (printing undefined or 12) is 
> inconsistent. One could defend throwing in the use-before-assignment 
> case, but it's odd. And throwing in both cases is the earlier 
> consensus semantics of temporal dead zone with a distinct state for 
> lack of initialization (even if the initialization is implicit, e.g., 
> in a declaration such as let x; being evaluated). Here 
> "initialization" is distinguished from assignment expressions 
> targeting the binding.
>
> Trying to be like var, printing undefined or 12, is possible but 
> future-hostile to guards and gratuitously different from const:
>
> {
>   x = 12;
>   const G = ...;
>   let x ::G = "hi";
> }
>
> We want to be future-proof for guards, and even more important: we 
> want to support *refactoring from let to const*. Ergo, only temporal 
> dead zone with its barriers is tenable.
>
> There remains an open issue: without closures obscuring analysis, it 
> is easy to declare use before initialization within the direct 
> expression-statement children of a given block to be early errors, 
> rather than runtime errors:
>
> {
>   x = 12;          // can be early error
>   print(x);        // can be early error
>   function f() {
>     return x;      // may or may not be error
>   }
>   escape(f);       // did this call f?
>   let x = 42;
>   escape2(f);      // did this call f?
> }
>
> Some on TC39 favor normative specification of early errors for the 
> easily-decided cases. Others want runtime-only error checking all 
> around and point out how even the easy cases (within straight-line 
> code in the block's direct expression-statement children) testing that 
> reaches the block will fail fast. The question remains: what if the 
> block is not covered by tests?
>
> Dave Herman brought up the let/var at top level equivalence 
> implemented in SpiderMonkey, specifically in connection with <script> 
> tags. Sketching in pseudo-HTML:
>
> <script type=harmony>
>   alert = 12;      // reassign built-in alert
> </script>
>
> <script type=harmony>
>   let alert = 13;  // shadow built-in alert
>   var quux = 14;   // this.quux = 14
>   let quux = 15;   // alternative: in scope for later scripts?
> </script>
>
> <script>
>   alert(quux);
> </script>
>
> Dave's point was not to commend the SpiderMonkey equating of let and 
> var at top level, but to observe that if "let is the new var", then 
> depending on how multiple successive script elements' contents are 
> scoped, you may still need to use var in Harmony -- let won't be 
> enough, if it binds only within the containing <script> element's scope.
>
> Recall that Harmony removes the global (window in browsers) object 
> from the scope chain, replacing it with a lexical environment with 
> (generally) writable bindings. Each script starts with a fresh lexical 
> environment, although it might be nested (see next paragraph).
>
> For scripts that do not opt into Harmony, there's no issue. The global 
> object is on the scope chain and it is used serially by successive 
> script elements.
>
> The question for Harmony scripts boils down to: should successive 
> Harmony scripts nest lexical scopes in prior scripts' scopes, like 
> matryoshka dolls? Or should each script opted into Harmony be its own 
> module-like scope, in which case to propagate bindings to later 
> scripts, one would have to
>
> <script type=harmony>
>   export let quux = 14; // available here and in later scripts
> </script>
>
> This remains an open question in TC39. Some liked the explicit 
> 'export' requirement, the implicit module scope. Others objected that 
> migrating code would expect the nested semantics, which was not 
> inherently evil or unsafe.
>
> --- end of block scope discussion ---
>
> /be
> _______________________________________________
> es-discuss mailing list
> es-discuss at mozilla.org
> https://mail.mozilla.org/listinfo/es-discuss
>


More information about the es-discuss mailing list