Live data from Hacker News

The Temporal Dead Zone, or why the TypeScript codebase is full of var statements

vincentrolfs.dev

41–50 of 64 posts

Re: The Temporal Dead Zone, or why the TypeScript codebase is full of var statements

#41
post #16
post #11

Earlier quoted context omitted.

That's not how lexical scope works anywhere but in JavaScript. Or rather, it's the interaction between "normal" lexical scope and hoisting. In a "normal" lexically scoped language, if you tried: function f() { return x; // Syntax parsing fails here. } let x = 4; return f(); you would get the equivalent of a ReferenceError for x when f() tried to use it (well, refer to it) at the commented line. But in JavaScript, thi…

That’s not the example I’m talking about. I mean where he defines `calculation` within the curly braces of the if statement, then says it “leaked out” because he can log it below the closing brace of the if statement. That’s a perfect example of the difference between lexical scope and block scope.

> the difference between lexical scope and block scope

There isn't a difference between lexical scope and "block" scope. What I think you are referring to as "block" scope, is a subset of lexical scope. The difference between var and let/const is where the boundaries of the lexical scope is.

Re: The Temporal Dead Zone, or why the TypeScript codebase is full of var statements

#42

Earlier quoted context omitted.

The interesting part here is that javascript interpreters not having to track the TDZ comes with an interesting performance bonus.

javascript interpreter* They are only talking about one javascript engine (node). They didn't test any other engine or go into the implementation in node. For all we know, this might just be a poorly optimized code path in node that needs a little love, but the author didn't bother doing any investigation. Looking at the linked github issue, jsc doesn't have the performance penalty. It would have been interesting if…

Maybe it could be optimized more but the TDZ fundamentally adds a performance cost, because it requires a runtime check to see if the variable has been initiatalized yet.

Also node used the v8, the same engine as chromium. So this doesn't just affect node, it also affects the majority of the browser share market. oh, and deno uses v8 too.

Re: The Temporal Dead Zone, or why the TypeScript codebase is full of var statements

#43
post #28

Indeed, `let`s and `const`s incur a significant performance penalty. This is also why the Scala.js compiler emits `var`s by default, even when targeting very recent versions of ECMAScript. The good news is that we can still write our Scala `val`s and `var`s (`const` and `let`) in the source code, enjoying good scoping and good performance.

`let`s and `const`s incur a significant performance penalty.

Is that still true? Early versions of V8 would do scope checks for things that weren't declared with var but it doesn't do that any more. I think const and let are lowered to var representation at compile time now anyway, so when the code is running they're the same thing.

Re: The Temporal Dead Zone, or why the TypeScript codebase is full of var statements

#44
post #11

Earlier quoted context omitted.

That's not how lexical scope works anywhere but in JavaScript. Or rather, it's the interaction between "normal" lexical scope and hoisting. In a "normal" lexically scoped language, if you tried: function f() { return x; // Syntax parsing fails here. } let x = 4; return f(); you would get the equivalent of a ReferenceError for x when f() tried to use it (well, refer to it) at the commented line. But in JavaScript, thi…

This is not some terrible decision that comes with only downsides. In fact there are quite a few upsides to the flexibility it brings compared to a language like Python that works as you describe. It basically means you can always override anything, which allows for monkey patching and proxying and adapter patterns and circular imports… These are all nasty things to accidentally encounter, but they can also be powerf…

Actually, let/const do the opposite of adding flexibility. Simple example: if you have a REPL, it has to cheat (as in, violate the rules of the language) in order to do something sensible for let/const. Once you do `let x;`, you can never declare `x` in the REPL again. In fact, simple typing `console.log(v)` is ambiguous: will you enter `let v` some time in the future or not?

You can't monkey patch lexicals, that is much of their point. Any reference to a lexical variable is a fixed binding.

In practice, this comes up most often for me when I have a script that I want to reload: if there are any toplevel `let/const`, you can't do it. Even worse, `class C { ... }` is also a lexical binding that cannot be replaced or overridden. Personally, I normally use `var` exclusively at the toplevel, and `let/const` exclusively in any other scope. But `class` is painful -- for scripts that I really want to be able to reload, I use `var C = class { ... };` which is fugly but mostly works. And yet, I like lexical scoping anyway, and think it's worth the price. The price didn't have to be quite so high, is all. I would happily take the benefit of avoiding TDZ for the price of disabling hoisting in the specific situations where it no longer makes sense.

I agree that hoisting is a backwards compatibility thing. I just think that the minute optional lexical scoping entered the picture, hoisting no longer made sense. Either one is great, the combination is awful, but it's possible for them to coexist peacefully in a language if you forbid the problematic intersection. TDZ is a hack, a workaround, not peaceful coexistence. (TDZ is basically the same fix as I'm proposing, just done dynamically instead of statically. Which means that JS's static semantics depend on dynamic behavior, when the whole point of lexical scoping is that it's lexical.)

Re: The Temporal Dead Zone, or why the TypeScript codebase is full of var statements

#45
post #16
post #11

Earlier quoted context omitted.

That's not how lexical scope works anywhere but in JavaScript. Or rather, it's the interaction between "normal" lexical scope and hoisting. In a "normal" lexically scoped language, if you tried: function f() { return x; // Syntax parsing fails here. } let x = 4; return f(); you would get the equivalent of a ReferenceError for x when f() tried to use it (well, refer to it) at the commented line. But in JavaScript, thi…

That’s not the example I’m talking about. I mean where he defines `calculation` within the curly braces of the if statement, then says it “leaked out” because he can log it below the closing brace of the if statement. That’s a perfect example of the difference between lexical scope and block scope.

Ah, fair, I didn't actually pay attention to which example you were referring to. That example is specifically about `var` being terrible, not `let/const`.

I was really using your comment as a jumping off point for my rant.

I wouldn't describe `var` declarations as lexical, though. Sure, they have a lexical scope that they get hoisted up to cover, but hoisting is not "just lexical scope". It's unusual.

Re: The Temporal Dead Zone, or why the TypeScript codebase is full of var statements

#46
post #44

Earlier quoted context omitted.

This is not some terrible decision that comes with only downsides. In fact there are quite a few upsides to the flexibility it brings compared to a language like Python that works as you describe. It basically means you can always override anything, which allows for monkey patching and proxying and adapter patterns and circular imports… These are all nasty things to accidentally encounter, but they can also be powerf…

Actually, let/const do the opposite of adding flexibility. Simple example: if you have a REPL, it has to cheat (as in, violate the rules of the language) in order to do something sensible for let/const. Once you do `let x;`, you can never declare `x` in the REPL again. In fact, simple typing `console.log(v)` is ambiguous: will you enter `let v` some time in the future or not? You can't monkey patch lexicals, that is…

> if there are any toplevel `let/const`, you can't do it [monkeypatch it]

True, but you can at least wrap the entire scope and hack around it. It's not gonna be pretty or maintainable but you can avoid/override the code path that defines the let.

Anecdotally... I've monkeypatched a lot of JavaScript code and I've never been stopped from what I wanted to do, whereas with Python I've hit a dead-end in similar situations. Maybe there's some corner case that's unpatchable but I really think there is always a workaround by the ability to wrap the scope in a closure. Worst case you re-implement the entire logic and change the bit you care about.

Re: The Temporal Dead Zone, or why the TypeScript codebase is full of var statements

#47
post #11

Earlier quoted context omitted.

That's not how lexical scope works anywhere but in JavaScript. Or rather, it's the interaction between "normal" lexical scope and hoisting. In a "normal" lexically scoped language, if you tried: function f() { return x; // Syntax parsing fails here. } let x = 4; return f(); you would get the equivalent of a ReferenceError for x when f() tried to use it (well, refer to it) at the commented line. But in JavaScript, thi…

This is not some terrible decision that comes with only downsides. In fact there are quite a few upsides to the flexibility it brings compared to a language like Python that works as you describe. It basically means you can always override anything, which allows for monkey patching and proxying and adapter patterns and circular imports… These are all nasty things to accidentally encounter, but they can also be powerf…

None of the things you mentioned are clearly related to each other. “It basically means you can always override anything, which allows for monkey patching and proxying and adapter patterns and circular imports” is not true. “They’re the reason why JavaScript can have multiple versions of the same package while Python cannot” is definitely not true. (I’m not even sure if you’re referring to TDZ or hoisting or lexical scope or whatever other part of the context, but these things are unrelated to every option.)

The premise of “a language like Python that works as you describe” is wrong too, since Python doesn’t work like that (it has the same hoisting and TDZ concepts as JavaScript):

  def g():
      def f():
          return x
  
      x = 4
      return f()
  
  print(g())  # 4

Re: The Temporal Dead Zone, or why the TypeScript codebase is full of var statements

#48
post #40
post #28

Indeed, `let`s and `const`s incur a significant performance penalty. This is also why the Scala.js compiler emits `var`s by default, even when targeting very recent versions of ECMAScript. The good news is that we can still write our Scala `val`s and `var`s (`const` and `let`) in the source code, enjoying good scoping and good performance.

I wonder how many companies are still using Scala.js. Scala was fun to work with, wish it was more popular these days.

Usage of Scala.js is steadily growing. Several indicators suggest that 1 in 5 Scala developers use Scala.js at this point. It's regularly brought up as one of the strongest suits of Scala.

Usage of Scala itself is less shiny if you look at market share. But I believe it's still growing in absolute numbers, only quite slowly.

Re: The Temporal Dead Zone, or why the TypeScript codebase is full of var statements

#49
post #43
post #28

Indeed, `let`s and `const`s incur a significant performance penalty. This is also why the Scala.js compiler emits `var`s by default, even when targeting very recent versions of ECMAScript. The good news is that we can still write our Scala `val`s and `var`s (`const` and `let`) in the source code, enjoying good scoping and good performance.

`let`s and `const`s incur a significant performance penalty. Is that still true? Early versions of V8 would do scope checks for things that weren't declared with var but it doesn't do that any more. I think const and let are lowered to var representation at compile time now anyway, so when the code is running they're the same thing.

I'm sure it can do that in many cases. But if the scopes are a bit complicated, and in particular when variables are captured in lambdas, it's just not possible. The semantics require the TDZ behavior. If you can statically analyze that the TDZ won't be triggered, you can lower to `var`, but otherwise you have to keep the checks.

Re: The Temporal Dead Zone, or why the TypeScript codebase is full of var statements

#50

Why wouldn't `let` be exactly what you want? It's block scoped but doesn't need fancy TDZ checks because like `var` it just starts out as undefined.

I think it’ll still throw a ReferenceError. Initialization is optional, but you still have to initialize before referencing.

Nope. `(() => {let bar; return bar})()` is `undefined`
Post reply on HN