Hacker News new | comments | ask | show | jobs | submit login
How necessary are var, let, and const? (raganwald.com)
89 points by tbassetto on June 1, 2015 | hide | past | web | favorite | 83 comments

I found this article's premises and findings rather pointless, and kept on wanting the author to instead verify the premise "are there actually valid uses of 'var' now that 'let' exists?" (I haven't found any yet, and I'd like to know if I'm wrong). But maybe I'm too much of a pragmatist rather than an academic, so I suppose it wasn't written for me.

Yes! Thank you. I agree the question is as you pose it -- why use `var`, ever?

"let", of course, is a retrofit to Javascript. So was "var". In the early days of Javascript, before "strict mode", few people declared anything and most variables had global scope.

It's really hard to get a declaration-free language right. Python was the biggest success to date.

Huh? `var` was not retrofitted. I distinctly remember it being there since I started learning JavaScript, like around '96. It is true that a lot of people were slack about using it, but that's just laziness, not that it wasn't there.

Sorry, what I meant was more that since we have let, there isn't any good reason to use var anymore. I definitely don't advocate the thing in this article where he eliminates var usage by littering the code with IIFEs.



I believe the parent comment was referring to using “let” instead of “var” - “let” supports reassignment.

Before ES6 im curious did you always need blocked scoped variables? Of course we used iife's when we needed to but how often did we need to do that compared to the times we used regular variables. Is it always worth the extra overhead to enforce everything being blocked scoped? What about times that we needed to alter something outside the scope? whether or not thats always a good idea is another discussion but there are valid uses. I remember people were getting all worked up over functional expressions vs declarations saying expressions were 'the one true way' since it more closely resemebles how the compiler reads it. As babel shows let and const are just var's with extra overhead. Dont get me wrong let and const are in the right direction but they are not 'the one true way'

const IS valuable and necessary.

1) It means that every place the const appears it can be replaced with the const's value. Type checking can be streamlined, less indirection.

2) It communicates the developer's INTENT in an enforced manner. No later code change can accidentally make the const suddenly non-const.

3) const + deep Object.freeze() makes for a complete const.

Sure a human could hypothetically scan the code to see that the value has not been reassigned. This falls apart in practice. As soon as the company has a few dozen developers the code is constantly changing and such manual analysis would have to be constantly done.

1.) is simply wrong. Const variables have a live-range hole where access to the variable either throws or returns undefined, depending on the language mode (and the VM!). In either case it not legal to just smash in the constant.

2.) That's nice but JS semantics do not actually hold up. The intent is for a constant, the reality is there is a small window where the wrong results can be observed. Engines don't actually do any more optimizations than for var or let; in fact, your code may actually be slower due to the live-range hole checks.

So you are saying that there are race conditions currently? Are any of this race conditions actually required by the language spec? If not, I suspect that those race conditions will be eliminated.

I believe that what the parent meant was that this:

function f() { console.log(a); const a = 1; console.log(a); }

...actually returns this:

undefined 1

So you get partially compile-time semantics (you can't assign to a) but partially run-time semantics (a only gets its value when initialised). Simply propagating the value of a's initialiser to all places where the variable is used is wrong, as this behaviour's required by the spec.

Actually that code example should throw an exception when the first `a` is evaluated.

It doesn't in v8 -- what do you mean by `should`?

It's an implementation error. In Firefox, it correctly throws:

> ReferenceError: can't access lexical declaration `a' before initialization

No, legacy const is not spec'd by ES5. It behaves differently in FireFox and V8 because it was added at different times with different semantics in the live range hole case. In strict mode, it will throw on V8 as well.

No it shouldn't. `a` at the first console.log invocation IS undefined, so it is entirely correct.

Not according to the spec[1]:

> The variables are created when their containing Lexical Environment is instantiated but may not be accessed in any way until the variable’s LexicalBinding is evaluated.

I.e. given a variable declared with `const`, you may not access it before it is set.

[1] http://people.mozilla.org/~jorendorff/es6-draft.html#sec-let...

Consts have other purposes: Speeding up global constants using fixed field optimization https://blogs.windows.com/msedgedev/2015/05/20/delivering-fa...

> 3) const + deep Object.freeze() makes for a complete const.

The trouble with this approach is that runtime checks are only a guarantee that the code you've _exercised_ does not break the contract.

In that respect, `const` is deeply different than `.freeze`, in the same way that runtime assertions are deeply different than type checking.

Huh? That's why I pointed out the joint usage. And either way, a instant failure is better than a silent change.

Aren't points 1 and 2 just arguments for a stricly typed language?

Not at all. Simple things like instanceof / typeof checks can be resolved without a static type system. Because with a const everything is known about the const in question. Obviously the const prototype may change but then again if the const has a frozen prototype - that too can have static analysis applied to it.

I am simply repeating the well-known benefits to immutables which is completely orthogonal to a type system, static or otherwise.

instanceof cannot be determined statically because I can do...

    const a = {};
    Object.setPrototypeOf(a, SomethingElse);

really? even on a frozen object?

Remember I have said const PLUS Object.freeze() Both are needed to get the true immutable effect.

isn't 2 kind of a misdirected optimization for an untyped language?

How is INTENT of immutability ( and with const - now an enforced immutability ) misdirected optimization in any language.

Javascript is dynamically weakly typed but it is still typed, typeof/instanceof exist for a reason.

sorry right- weakly typed.

my point is if you are using a weakly typed language, it doesn't care much about developer intent with respect to type. so why would it be concerned with developer intent with respect to mutability? seems arbitrary.

maybe because developers in general prefer to not have bugs?

Hmm, posts like this make me want to make Lisp mandatory for all programmers. Given that LISPers have argued for "lambda the ultimate kitchen utensil/sinks/cooks" for the last few decades, it's slightly annoying that people from other languages repeat the point now. That said, congratulation for learning value of syntactical sugar. I hope you like it.

That said, can somebody implement hygienic macros in JavaScript. They will probably help will people having to explain basics knowledge over and over.

> Given that LISPers have argued for "lambda the ultimate kitchen utensil/sinks/cooks" for the last few decades, it's slightly annoying that people from other languages repeat the point now.

Why? Are you also annoyed by calculus students who learn ideas today that have been known for hundreds of years?

> That said, congratulation for learning value of syntactical sugar. I hope you like it.

Maybe I am misreading this (it could mean simply what it literally says), but I read it as unconstructive snark.

I suspect that if mathematicians who were creating alternate number systems without calculus were continually learning the usefulness of calculus and telling the world, then yes, those who advised its inclusion for years would be annoyed, if only because it took so damn long and so much time was wasted.

Assuming that one is a fan of the sort of abstract computer-scientific approach to programming embodied by Lisp (and I am!), then it should be a good thing when people discover it.

Is there a benefit in spreading this discovery to others, even after it's been known so long? Certainly I think so; it's new to everyone at some point, and, since I wasn't originally motivated to read McCarthy's original papers, I wouldn't have discovered the ideas of Lisp without recent expositions of it. Granting the benefit of this, who can better communicate with the unenlightened, in terms familiar to them, than a recent convert? (Although, as braythwayt points out (https://news.ycombinator.com/item?id=9641279), he is scarcely a newcomer to the party.)

Given that LISPers have argued for "lambda the ultimate kitchen utensil/sinks/cooks" for the last few decades, it's slightly annoying that people from other languages repeat the point now.

Personally I find excessive use of lamdas create unmaintainable and incredibly difficult to debug spaghetti code. The idea that JS developers should make their code even more LISP-like is enough to give me nightmares.

"I used to brag that I could 'write Lisp in any language.' But Lispers doubted that I could, and everyone else feared that I would."

What gets me is that there are languages in 2015 that don't get lexical scoping right. It has been a solved problem for 40 years now.

Saying that it's 2015 is a bit misleading. In JS's case, it's mostly a question of backwards compatibility and interactions with those backwards-compatible features.

The designers of let are largely a bunch of Schemers; they knew what they were doing.

I see why my post was confusing. Let me clarify: I was referring to languages like Coffeescript, not let in JS. Though JS got scoping wrong 20 years after it was a solved problem...

FWIW, I learned Scheme in the mid 1980s, loooooong before I'd ever heard of JavaScript.

> That said, can somebody implement hygienic macros in JavaScript.

Although I haven't used it hands-on, http://sweetjs.org/ looks impressive.

For example recently sweet.js apparently implemented some cutting-edge Racket macro developments before Racket officially did. :) https://github.com/mozilla/sweet.js/pull/461

Agreed. If JavaScript only had a hygienic macro system I could have written 'let' (and a whole bunch of other forms) for myself a long time ago.

Interesting and well written; if it were me, I might only change the ending a bit:

"Whereas, const […]. It is not nearly as useful as immutable data, because the problem it solves is easy, not hard."

To something like:

"const may be more useful than adding something like immutable data to an imperative language with mutability deep in its DNA might be because it helps with one of the hardest problems; long-term, correct re-reading by humans doing maintenance or extension."

But, the OP covers that well earlier in, so I'm not sure a third take on it would add much. Thanks for doing the exploration!

Can you eliminate `var` by introducing an unnecessary function, declaring the `var`s as parameters, and then invoking? Yes you can.

Why you would ever do that, however...

This was my thoughts exactly. I stopped reading after that section. I don't understand what the author is trying to do.

const can be used to denote that a reference to the variable is being passed around and should not change for the lifetime of a scope.

    function f() {
      const queue = [];
      return queue;
It could be important to note that removing const has benign effect, but that it is there to ensure a programmer does not change the value of queue between `consumer(queue)` and `return queue`. Easily reasoned about, but extra safeguards from introducing bugs is always nice.

I'm not sure I quite understand your point - while it's true that the variable called queue won't be reassigned, const would still allow elements and properties to be added to queue.

const is not about immutability in JS.

I know that - but the previous comment made it sound as though it did mean immutability.

Ironically enough, but your example shows where const is shouldn't be used as guarantee of immutability: https://t.co/JIQozGe7Md

Isn't that a typical "corner case" in the understanding of dynamic languages bindings ? I think I've seen the same question pop about python. The array binding is constant, its content isn't.

if value of constant can be changed, it's not constant. In my understanding. And by Wikipedia: http://en.wikipedia.org/wiki/Constant_(computer_programming)

That's typical computer lingo / semantic issue. the `const` mean shallow/pointer constant, not deep/structurally constant.

const is not about immutability in JS.

I guess you can say that they are there to allow the compiler to check the intent of the programmer.

If we use the approach of converting var's to named parameters, we also have to give up on passing variable numbers of arguments to functions. That is, unless we do something silly like:

    function silly(x, y, z, args) {
      args = Array.prototype.slice.call(arguments);
      x = y = z = undefined;

      // regular function goes here

The replacement of var or let was a thought experiment to show the actual value--in code--of using them.

Does anyone else out there think Javascript `const` should really be called `final`? It more closely mimics Java's `final` keyword by preventing reassignment ("rebinding") of the variable, whereas `const` makes me think of full immutability as in C++.

OK, I'll bite: what does De Stijl (http://en.wikipedia.org/wiki/De_Stijl) have to do with any of this?

Perhaps the question of whether `const` in a language that doesn't do much static type checking/analysis is a question of style, and not a clear across-the-board win.

It was a movement that attempted to achieve clarity and elegance by omitting the unnecessary -- and the article discusses whether omitting formally-unnecessary features like var or const makes for more clarity and elegance, or less.

(Having said which, I notice that there's a reply from the person who actually wrote the article, and it doesn't say that. But that's why he should have made the reference :-).)

I might have said that, but then I would be omitting the fact that not everyone finds my reproduction of the red-=blue chair comfortable.

Scheme vs. Common Lisp, and all that.

To be fair; getting the benefit of const would require code too though:

  function constUseful () {
    var guarded = 1;

    (function loop (orgGuarded) {
      setTimeout(function () {
        if (guarded !== orgGuarded) {
          throw 'guarded reassigned!';
      }, 0);

    guarded = 2;

That's an approximation of const's value as delineated in code.

A better question is how useful is let at all? Having two different variable scoping rules with no way to know which one a variable is using without backtracing sounds like a maintenance nightmare. I understand what is it good for, but it seems like we are replacing a small problem with a bigger problem.

An even better question is why not get rid of var and make every variable local to its parent function? To alter globals, use the window object explicitly.

> better question is how useful is let at all? Having two different variable scoping rules with no way to know which one a variable is using with backtracing sounds like a maintenance nightmare.

I agree, but I think the solution is to stop using `var`, the question is how useful `var` really is. `let`'s scoping is what it appears to be, `var`'s is not. So `let` is the better choice.

I disagree. Maybe you think this because you are used to another language, but once you get how var works, it's quite clear. Just because some other language does it, does not mean it is unquestionably good.

If Javascript didn't have closures, then var's scoping rules would largely be academic.

But in practice, there are many cases when I have to create multiple callbacks in a loop; half the time I forget that, in Javascript, declaring a variable inside a loop does not actually create a variable local to the loop, and so have to keep debugging why all my callbacks are behaving exactly the same.

The let statement appears to adopt Lua-style lexical scoping, which will be a breath of fresh air once I can rely on browser support.

On the contrary, I pretty much only write JS and have for years now. And I think `var` has warped our brains, not the other way around.

I think the answer is to just use let. In an ideal world they'd just change the behaviour of var to something sensible, but they (wisely) wanted to retain backwards compatibility.

const is a different case, but IMO there's no need to use var when let exists.

> To alter globals, use the window object explicitly.

JS is used in non-DOM contexts as well. Though, of course, if that big a change is being made, a generic substitute for `window` could also be specified.

> a generic substitute for `window` could also be specified

Isn't that just |this| at the top level?

Not necessarily the _top_ level if you are referring to scope. `this` will be the default global unless another context is defined, whether you are at the top level or inside a function.

In node, there is a name for the default global: "global", which is also the default context. So node gives it a "generic substitute" as the OP suggested. Most people use "this" to make assignments to it, rather than the "global" name, since it is usually accessed from the default context.

Could anyone please explain to me why the author chose those functions for his examples? I got as far as callFirst() and repeat() and found it hard enough to wrap my mind around the functional / meta programming within. Are those actually good examples? It seems like there are simpler ways to illustrate the point.

If you check out Javascript Allonge (linked on the page, web version is free) you'll find out. The author has written a lot about functional programming, combinators and so on in JS.

I think his point was simply that examining how to emulate let/var with closures, it shows you explicitly what is different/similar about them. He's not suggesting anyone use those transforms.

let is obviously needed(devs like lexical scoping) but I found the whole var + let in the same language thing a bit confusing. The temptation to introduce yet another strict mode(to forbid var,and a few other constructs) is strong on this one.

That is not at all obvious to many long time JavaScript engineers. Having a variable scoped by block or by function works for me, having both in the language is a pain. I have seen some neat tricks that you can do with let in for loops, but nothing that has convinced me it's worth the cognitive overhead of figuring out is this variable a var or a let.

My opinion is that if you use let, don't use var, and if you use var, don't use let. Either one on its own is fine, together they are just a mess.

var vs. let in the same language is a consequence of designing the language by committee, requiring 100% backwards-compatibility, and forbidding the addition of new modes (which is a back-door way to introduce breaking changes, while requiring browser vendors to essentially support multiple versions).

That behaviour is not exclusive to standards committees. The government and private companies do it all the time.

"governments and private companies" certainly do not preclude committees. Quite to the contrary, in fact.

>The temptation to introduce yet another strict mode(to forbid var,and a few other constructs) is strong on this one.

There is a proposal for that, coincidentally called "strong mode".


> You can see every single rebinding of a variable within the lexical scope of the function,

I do not think that is true, a called function can side effect the caller environment via `arguments` if that is passed around.

My understanding is that arguments caller has been removed, and Function.prototype.caller is non-standard:

  > The obsolete `arguments.caller` property used to provide the
  > function that invoked the currently executing function. This
  > property has been removed and no longer works.

Can you provide an example of modifying a declared variable in a calling environment? I'm very interested!

I meant this:

    function sidEffecting(ary) {
      ary[0] = ary[2];
    function bar(a,b,c) {
      c = 10
      return a + b + c;
    bar(1,1,1) // I expect 12, but is 21
I now realize this could actually be not allowed anymore in recent ES, I am not sure, but copy & paste in chrome console still prints 21.

That disclaimer is a bit of a cop-out, given the subject matter..

Guidelines | FAQ | Support | API | Security | Lists | Bookmarklet | Legal | Apply to YC | Contact