local variables with inherited values

# raul mihaila (12 years ago)

Did you consider adding a way for variables to automatically get the value from a variable with the same name from the parent execution context?

(function () {
    var x = 2;

    (function () {
        local x; // x = 2
        x = 100;
        x; // x = 100

        local y; // maybe throw since y was not found anywhere
    })();

    x; // x = 2
})();

This would be useful when optimizing the code so that the variables are as local as possible. Especially useful with calling functions (multiple times) or working heavily with collections from the outer context in the inner functions.

# Alex Kocharin (12 years ago)

An HTML attachment was scrubbed... URL: esdiscuss/attachments/20131224/58c2a395/attachment

# Brendan Eich (12 years ago)

ES4 had let (x = x) ... forms (where ... was an expression or a body), but for ES6 we agreed on the temporal dead zone:

{ let x = 'outer'; { /* x throws here */; let x = 'inner'; alert(x); } alert(x); }

which will alert 'inner' then 'outer'. There is no way to initialize the inner x from the outer. The reason for temporal dead zone is given by counterexamples against the alternatives. Quoting from Waldemar Horwant at esdiscuss/2008-October/007807


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).


Raul, you seem to want A2 or possibly B2, I'm not sure which. I hope this helps make the case for B1, temporal dead zone.

It may seem unusual compared to languages that do not have closures, or that require declarations to come before any closures.

# raul mihaila (12 years ago)

What I want is both similar to A2 and B2, but it's different in that there is only 1 scope for the inner variable. The inner variable is just a local variable, but it's initialized in the hoisting process with the value from the outer scope. Initially I was thinking only about var scoped variables but I think this should work for the let scoped variables as well. We would need a new keyword then. Adds some complexity but at least there's only one scope.

I didn't study the details about let scope variables in the current spec but I understand that this should throw:

{
    (function () {
        x; // throws - B1 case
    })();
    x; // x = undefined, right? - not A1 case because it doesn't throw
    let x = 10;
};

Currently in FF there's no error here, x is undefined. In Chrome there's an error about some extended mode which, from what I understand, doesn't exist anymore.

Currently I achieve what I want by defining a local variable whose name is prefixed with 'local' and assigning it the value I need. It's bad if I want to do the same in multiple execution levels. My proposal is far more readable because it looks less weird.

# raul mihaila (12 years ago)

On Tue, Dec 24, 2013 at 7:53 PM, Brendan Eich <brendan at mozilla.com> wrote:

ES4 had let (x = x) ... forms (where ... was an expression or a body), but for ES6 we agreed on the temporal dead zone:

Or maybe there's no need for a new keyword :). Not sure I would like an additional body though.

# Brendan Eich (12 years ago)

raul mihaila wrote:

What I want is both similar to A2 and B2, but it's different in that there is only 1 scope for the inner variable.

There is only one inner scope for the braced block in Waldemar's example, in any of {A,B}{1,2}:

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();
}

Note that function-in-block declarations hoist to top of block -- I should have cited that too, for emphasis, in my last message's closing.

The inner variable is just a local variable, but it's initialized in the hoisting process with the value from the outer scope.

That's unusual -- why would it take on the out same-named variable's value? Note it won't work for 'const', only 'let'. Is there any difference for 'let' between your proposal and A2, lexical window? Let's see, by simplifying the example to make the inner 'const x' into a 'let x':

let x = "outer";
function g() {return "outer"}

{
   g();
   function f() { ... x ... g ... g() ... }
   f();
   let x = "inner";
   function g() { ... x ... }
   g();
   f();
}

Your proposal sounds equivalent to this:

{
   let x = outer`x`; // quasi syntax for outer x
   g();
   function f() { ... x ... g ... g() ... }
   f();
   x = "inner";
   function g() { ... x ... }
   g();
   f();
}

Notice the call g() in f's body, and the call f() before x = "inner". This means that f captures (closes over) the block-local x, but its value depens on when f is called. The call of f(); right before end of block sees x = "inner". The call from just before x = "inner"; sees "outer", as does the reference to x in g of course (that capture is not in question).

Initially I was thinking only about var scoped variables but I think this should work for the let scoped variables as well. We would need a new keyword then. Adds some complexity but at least there's only one scope.

There is only one block scope in any event. I am not sure what you mean here.

In any case, we aren't adding another keyword, and your approach does not work for 'const' at all. For 'let' or any new keyword, it hoists and implicitly initializes, which is too implicit and magical, even for JS.

I didn't study the details about let scope variables in the current spec but I understand that this should throw:

{
    (function () {
        x; // throws - B1 case
    })();
    x; // x = undefined, right? - not A1 case because it doesn't throw
    let x = 10;
};

First, you don't show any outer x at all, so this involves no inner/outer shadowing/windowing/dead-zoning.

Second, the x; before let x = 10; would throw per current spec. I don't know what you mean by "not A1" in the comment, since the current spec doesn't implement A1 (lexical dead zone), it implements B1 (temporal dead zone).

Currently in FF there's no error here, x is undefined.

Please don't go by implementations yet. They are in various states with respect to the ES6 draft spec. Firefox's 'let' dates back to ES4 proposals. It will be updated.

In Chrome there's an error about some extended mode which, from what I understand, doesn't exist anymore.

There should be a temporal dead zone error, if you enable the flag for harmony_let.

Currently I achieve what I want by defining a local variable whose name is prefixed with 'local' and assigning it the value I need. It's bad if I want to do the same in multiple execution levels. My proposal is far more readable because it looks less weird.

It's far less readable, though, because of the implicit magic initialization from outer shadowed x. What if there is no outer x? And 'const' does not work at all. We can't have two values for a given 'const' binding.