Live data from Hacker News

On JavaScript's Weirdness

stack-auth.com

31–40 of 85 posts

Re: On JavaScript's Weirdness

#31
> In any programming language, when you capture values with a lambda/arrow function

It seems like just a few years ago that few programmers knew what these concepts are: mostly just the few that were exposed to Lisp or Scheme in college.

Now it's in "any language" and we have to be exposed to incorrect mansplaining about it from a C++ point of view.

> there are two ways to pass variables: By value (copy) or by reference (passing a pointer). Some languages, like C++, let you pick:

Lexical capture isn't "pass".

What this person doesn't understand is that C++ lambdas are fake lambdas, which do not implement environment capture. C++ lambdas will not furnish you with a correct understanding of lambdas.

(Furthermore, no language should ever imitate what C++ has done to lambdas.)

Capture isn't the passage of arguments to parameters, which can be call by value or reference, etc. Capture is a retention of the environment of bindings, itself.

The issue here is simply that

1. The Javascript lambda is correctly implementing lexical capture.

2. The Javascript loop is not creating a fresh binding for the loop variable in each iteration. It binds one variable, and mutates its value.

Mutating the value is the correct thing to do for a loop construct which lets the program separately express initialization, guard testing and increment. The semantics of such a loop requires that the next iteration's guard have access to the previous iteration's value. We can still have hacks under the hood so that a lexical closure will capture a different variable in each iteration, but it's not worth it, and the program can do that itself. Javascript is doing the right thing here, and it cannot just be fixed. In any case, vast numbers of programs depend on the variable i being a single instance that is created and initialized once and then survives from one iteration to the next.

Now lambdas can in fact be implemented by copy. Compilers for languages with lambda can take various strategies for representing the environment and how it is handled under capture. One possible mechanism is conversion to a flattened environment vector, whereby every new lambda gets a new copy of such a vector.

The entire nested lexical scope group becomes one object in which every variable has a fixed offset that the compiled code can refer to. You then have to treat individual variables in that flat environment according to whether any given variable is shared, mutated or both.

The worst case is when multiple closures capture the same variable (it is shared) and the variable is mutated such that one closure changes it and another one must see the change. This is the situation with the loop index i variable in the JS loop. This means that under a flat, copied environment strategy, the variable will have to be implemented as a reference cell in the environment vector. Variables which are not mutated can just be values in the vector. Variables which are mutated, but not shared among closures, likewise.

This is all under the hood though; there are no programmer-visible annotations for indicating how to treat each captured variable. It always looks as if the entire environment at the point of capture is being taken by reference. The compiler generates reference semantics for those variables which need it.

At the implementation level, with particular strategies for handling environments under lambda, we can think about capturing references or value copies. C++ lambdas imitate this sort of implementation-level thinking and define the language construct around it, in a way that avoids the master concept of capturing the environment.

Re: On JavaScript's Weirdness

#32

Many mistakes in section 2. The author seems to fundamentally misunderstand block scoping vs lexical scoping, and interactions when deferring execution to the next run of the event loop. In the first example: for (let i = 0; i console.log(i)); } // prints "0 1 2" — as expected let i = 0; for (i = 0; i console.log(i)); } // prints "3 3 3" — what? i's scope is outside the for loop in the second example, and the setTime…

The author's explanation seems perfectly correct to me. Where does he "misunderstand block scoping vs lexical scoping"? By the Wikipedia definition: > lexical scope is "the portion of source code in which a binding of a name with an entity applies". ...both `let` and `var` are lexically scoped, the scopes are just different.

FWIW, I think the parent meant "function scoping vs lexical scoping" rather than "block scoping vs lexical scoping". You're correct that function scoping is technically a form of lexical scoping (where the scope is the function), but if you want to be _really_ pedantic, the ecma262 considers let/const to be "lexical binding"[0] as opposed to var being a "variable declaration"[1], where the former declares in the "Lexical Environment" while the latter declares in the "Variable Environment". These happen to be the same environment record on function entry.

[0] https://tc39.es/ecma262/#sec-let-and-const-declarations [1] https://tc39.es/ecma262/#sec-variable-statement

Re: On JavaScript's Weirdness

#33

Many mistakes in section 2. The author seems to fundamentally misunderstand block scoping vs lexical scoping, and interactions when deferring execution to the next run of the event loop. In the first example: for (let i = 0; i console.log(i)); } // prints "0 1 2" — as expected let i = 0; for (i = 0; i console.log(i)); } // prints "3 3 3" — what? i's scope is outside the for loop in the second example, and the setTime…

The author's explanation seems perfectly correct to me. Where does he "misunderstand block scoping vs lexical scoping"? By the Wikipedia definition: > lexical scope is "the portion of source code in which a binding of a name with an entity applies". ...both `let` and `var` are lexically scoped, the scopes are just different.

However, there's no notion of the first example operating on a single scope and the latter on three different, individual scopes. Which is why scope ranges and where you declare a variable with `let` matters.

Re: On JavaScript's Weirdness

#34
The eval thing is listed as a potential performance cost, but it's actually super important for performance, because it allows the parser to statically know that sloppy eval is never called inside a function, and that variables can therefore be optimized away.

Re: On JavaScript's Weirdness

#35

The list of "other weirdness" at the end mentions: > +0 vs. -0 Which feels kind of odd, since that's mostly floating point weirdness, not JS weirdness. Unless they mean the fact that you can force V8 to initialize a zero-value as a double using -0 as a literal (it tends to optimize 0 to integers). But that has no effect on real-world code unless you use Math.sign, or divide by minus zero.

Fun fact: JavaScript 1.2 did feature -0 and 0 = +0. Which has quite a number of rather confusing aspects and effects, if you run a script in this context.

Re: On JavaScript's Weirdness

#36

While interesting and possibly helpful to new coders, are these quirks of the language still relevant when most development in Javascript is done using a framework (React, Vue, etc) these days? How often do these "gotchas" factor into "modern" Javascript development, especially in production? These type of articles seem to critique mechanics of the language that don't come up as often in practice.

The issue with variables and loops that OP described is worse with React, since you create closure inside the render function for event handlers, and if that closure captures any state variables (rather than a getter function for example), then you'll end up referencing stale state. React relies on linters to protect against this, but that only goes so far and the API design makes it easy to screw up, so you have to be on your toes.

Edit: To be clear, this is specifically a React hooks problem, not the old React with classes.

Re: On JavaScript's Weirdness

#37
post #13

“JavaScript sucks because '0' == 0!” - literally everyone ever I never really understood the hate for this, given that everything is a string in HTTP, and that SQL does the same damn thing. There are far more annoying things about JS (both the language and the ecosystem).

Moreover, it was kind of a standard for any scripting language at that time. In other words, this was generally expected behavior. (E.g., compare AWK, Perl, etc.)

Re: On JavaScript's Weirdness

#38

I've a few opinions on the content, but I'm most interested in which unicode analyzer tool generated that nifty text tree diagram. I can deal with JS's warts because the tooling is so much better. The other language I make my living with is PHP, and while it's much improved from before, the global function namespace is still a garbage fire, and you need extra linters like phpstan-safe-rule to force you to use sane wr…

Both JS and PHP are rather footgun-rich languages; have you tried Python, Java, Kotlin, or C#?

Re: On JavaScript's Weirdness

#39
post #32

Earlier quoted context omitted.

The author's explanation seems perfectly correct to me. Where does he "misunderstand block scoping vs lexical scoping"? By the Wikipedia definition: > lexical scope is "the portion of source code in which a binding of a name with an entity applies". ...both `let` and `var` are lexically scoped, the scopes are just different.

FWIW, I think the parent meant "function scoping vs lexical scoping" rather than "block scoping vs lexical scoping". You're correct that function scoping is technically a form of lexical scoping (where the scope is the function), but if you want to be _really_ pedantic, the ecma262 considers let/const to be "lexical binding"[0] as opposed to var being a "variable declaration"[1], where the former declares in the "Lex…

Thanks for the links, that adds a lot of important context.

Re: On JavaScript's Weirdness

#40

Earlier quoted context omitted.

This sort of stuff is very explicit and unsurprising in C++ (and to a lesser extent Rust), but it's always confusing in languages that leave the capturing details implicit. Even Go got bitten by this and it doesn't even JavaScript's broken `var`.

I don't think it's fair to call Go and Javascript's behavior "implicit", they just always capture variables by reference. Rust variable capture is implicit though, but it can't cause the problems described in the article, since mutable references are required to be unique.

In JavaScript, a 'let' inside the initializer of a for loop is captured by value, all the others are captured by reference.

I think it's fair to call that semantics "implicit".

Post reply on HN