let lhs = rhs; statements
       // non-recursive, scope is statements

   let { declarations }; statements        // recursive, scope is
                                                              // declarations 
and statements

   let { // group of mutually recursive bindings, *no statements*

       [x,y] = [42,Math.PI]; // initialization, not assignment

       even(n) { .. odd(n-1) .. } // using short method form
       odd(n) { .. even(n-1) .. } // for non-hoisting functions

       class X { .. }
       class C extends S { .. new X( odd(x) ) .. }
       class S { }
   };
   if (even(2)) console.log(  new C() );

First of all, this requires whole new syntax for the let body.

Yes and no - I'm borrowing definition syntax from other parts of
the language. Part of the appeal of having a declarations-only block
was to be able to use things like short method form there. The main appeal was to have no statements or hoisted constructs between declarations in a "letrec".

[by separating recursive and non-recursive forms, the non-recursive
form would have no rhs-undefineds for the ids being defined, which
would circumvent the separate, lexical form of dead zone]

Second, it doesn't eliminate the need for temporal dead zones at all.

You could well be right, and I might have been misinterpreting what
"temporal dead zone" (tdz) means. For a letrec, I expect stepwise-refinement-starting-from-undefined semantics, so I can use a binding anywhere in scope but may or may not get a value for it. While the tdz seems to stipulate that a binding for a variable in scope doesn't really exist and may not be accessed until its binding (explicit or implicitly undefined) statement is evaluated.

So what does it gain? The model we have now simply is that every scope is a letrec (which is how JavaScript has always worked, albeit
with a less felicitous notion of scope).

That is a good way of looking at it. So if there are any statements
mixed in between the definitions, we simply interpret them as
definitions (with side-effecting values) of unused bindings, and

{ let x = 0;
 let z = [x,y]; // (*)
 x++;
let y = x; let __ = console.log(z);
}

is interpreted as

{ let x = 0;
 let z = [x,y]; // (*)
 let _ = x++;
 let y = x;
 let __ = console.log(z);
}

What does it mean here that y is *dead* at (*), *dynamically*?
Is it just that y at (*) is undefined, or does the whole construct throw a ReferenceError, or what?
If tdz is just a form of saying that y is undefined at (*), then I can
read the whole block as a letrec construct. If y cannot be used until its binding initializer statement has been executed, then I seem to have a sequence of statements instead.

Of course, letrec in a call-by-value language with side-effects is tricky. And I assume that tdz is an attempt to guard against unwanted surprises. But for me it is a surprise that not only can side-effects on the right-hand sides modify bindings (x++), but that bindings are interpreted as assignments that bring in variables from the dead.

The discussion of dead zone varieties in

https://mail.mozilla.org/pipermail/es-discuss/2008-October/007807.html

was driven by the interplay of old-style, hoisted, definitions with
initialization desugaring to assignment. The former mimics a letrec,
with parallel definitions, the latter means a block of sequential
assignments.

So I was trying to get the old-style hoisting and initialization by
assignment out of the picture, leaving a block of recursive
definitions that has a chance of being a real letrec. Perhaps
nothing is gained wrt temporal dead zones. But perhaps this is a way to clean up the statement/definition mix, profit from short definition forms and provide for non-recursive let without
lexical deadzone.

Claus

_______________________________________________
es-discuss mailing list
[email protected]
https://mail.mozilla.org/listinfo/es-discuss

Reply via email to