On dropping @names

Andreas Rossberg rossberg at google.com
Tue Dec 11 02:45:55 PST 2012


On 10 December 2012 21:59, Claus Reinke <claus.reinke at talk21.com> wrote:
>> 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.

Not sure what you mean by
"stepwise-refinement-starting-from-undefined". JavaScript is both
eager and impure, and there is no tradition of imposing syntactic
restrictions on recursive bindings. Consequently, any binding can have
effects, and the semantics must be sequentialised according to textual
order. Short of sophisticated static analysis (which we can't afford
in a jitted language), there is no way to prevent erroneous forward
accesses from being observable at runtime.

The question, then, boils down to what the observation should be: a
runtime error (aka temporal dead zone) or 'undefined'. Given that
choice, the former is superior in almost every way, because the latter
prevents subtle initialisation errors from being caught early, and is
not an option for most binding forms anyway.

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

Exactly. At least that's my preferred way of looking at it.

> 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?

Throw, see above.

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

It inevitably is an _impure_ letrec, which is where the problems come in.

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

They are initialisations, not assignments. The difference, which is
present in other popular languages as well, is somewhat important,
especially wrt immutable bindings. Furthermore, temporal dead zone
also applies to assignments. So at least, side effects (which cannot
easily be disallowed) can only modify bindings after they have been
initialised.

None of these problems would go away by having explicit recursion.
Unless you impose far more severe restrictions.

/Andreas


More information about the es-discuss mailing list