6 ms·
The first example is not “terrible”, that’s just how lexical scope works. I don’t really see the point of complaining about language features like this - either
by taejavu 1y ago
The first example is not “terrible”, that’s just how lexical scope works. I don’t really see the point of complaining about language features like this - either learn how it works or ignore at your peril.
- throw-the-towel 1y agoI second that, I actually don't understand why do people believe every pair of curly braces has to be its own separate scope. An explicit construct for scoping would have been so much clearer to me.
- samus 1y agoIn most languages, each block indeed is a separate scope. And it avoids foot guns about accidentally using variables that already serve another purpose. I guess it's one of the things that are typical for dynamic languages.
- Aurornis 1y ago> I actually don't understand why do people believe every pair of curly braces has to be its own separate scope. It’s much easier to reason about when your variables aren’t going to escape past the end of the block. In non-GC languages going out of scope can also be a trigger to free the contents of the variable. This is useful for situations like locking where you can put the minimal span of code that requires the lock into a scope and take a lock which automatically unlocks at the end of the scope, for example. JavaScript’s hoisting and scoping feel natural to people who started in JS, but most people who came from other languages find it surprising.
- mjmas 1y agoGCed languages often also have a mechanism for running code on scope exit. For example Lua's <close> attribute, which runs a metamethod when it goes out of scope rather than having to wait for GC: function readfile(name) local f <close> = assert(io.open(name)) return assert(f:read"a") end
- thaumasiotes 1y ago> An explicit construct for scoping would have been so much clearer to me. What would be the advantage over the system used everywhere else?
- happytoexplain 1y ago>I actually don't understand why do people believe every pair of curly braces has to be its own separate scope To avoid having to memorize yet one more thing that doesn't have an obvious benefit. >An explicit construct for scoping would have been so much clearer to me Having an additional construct for scoping is clearer than having every set of already-existing curly braces be a new scope? That seems backwards.
- deleted 1y ago[deleted]
- 1718627440 1y ago> An explicit construct for scoping would have been so much clearer to me. Yes, maybe we could use something similar to parenthesis. Maybe they can look curly. /s
- sfink 1y agoThat'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, this successfully returns 4, because `let` inherits the weird hoisting behavior of `var` and `function`. And it has to, because otherwise this would be really weird: function f1() { return x; } let x = 4; function f2() { return x; } return Math.random() < 0.5 ? f1() : f2(); Would that have a 50/50 chance of returning the outer x? Would the engine have to swap which x is referred to in f1 when x gets initialized? TDZ is also terrible because the engines have to look up at runtime whether a lexical variable's binding has been initialized yet. This is one reason (perhaps the main reason?) why they're slower. You can't constant fold even a `const`, because `const v = 7` means at runtime "either 7 or nothing at all, not even null or undefined". In my opinion, TDZ was a mistake. (Not one I could have predicted at the time, so no shade to the designers.) The right thing to do when introducing let/const would have been to make any capture of a lexical variable disable hoisting of the containing function. So the example from the article (trimmed down a little) return Math.random() < 0.5 ? useX() : 1; let x = 4; function useX() { return x; } would raise a ReferenceError for `useX`, because it has not yet been declared at that point in the syntactic scope. Same with the similar return Math.random() < 0.5 ? x : 1; let x = 4; which in current JavaScript also either returns 1 or throws a ReferenceError. I'm not against hoisting functions, and removing function hoisting would have not been possible anyway. The thing is, that's not "just a function", that's a closure that is capturing something that doesn't exist yet. It's binding to something not in its lexical scope, an uninitialized slot in its static environment. That's a weird special case that has to be handled in the engine and considered in user code. It would be better to just disallow it. (And no, I don't think it would be a big deal for engines to detect that case. They already have to compute captures and bindings.) Sadly, it's too late now.
- deleted 1y ago[deleted]
- taejavu 1y ago
- happytoexplain 1y agoI think it's reasonable to have the opinion that the way lexical scoping works in JS is "terrible". You may disagree, but "that's just how it works" isn't a good argument. That line of reasoning is often a rationalization that we make when we are very used to a technology - a sort of hostage situation.
- jvanderbot 1y agoIn particular if it violates the assumptions of any non native programmer, then it's fair game for gripes.
- bigstrat2003 1y agoI mean, it certainly is surprising to me that you can use the variables before they are declared (albeit with undefined values).