Hacker News new | past | comments | ask | show | jobs | submit login
Constantly Confusing ‘const’ (getify.com)
33 points by _getify on Sept 8, 2015 | hide | past | favorite | 51 comments



I don't agree with this style.

`var` is broken. Don't try to find a use-case for it. Just don't use it.

It would be nicer to have real, deep immutability, but `const` isn't it. Therefore, use it for what it actually is, not what you wish it was. If you don't intend to reassign a reference, then mark the reference constant. That's it.

It may be surprising, but it's quite easy to write JS where almost every declaration is `const`. When every mundane variable is `const`, then the more complex initializations/data flows/accumulators really requiring `let` stand out, which is very useful for reviewing code ("let" = "warning: there may be an `else` or missing `switch` case that will leave that undefined").

Because `const` requires to be initialized at the time of definition, you know the variable is valid for its entire lifetime. When you find its value was wrong, there's only one place where the value could have come from (you don't need to comb the function to look for reassignments).


> `var` is broken

Nonsense. Revisionism at its finest.

But that's an argument I made in a different blog post: http://davidwalsh.name/for-and-against-let

> It would be nicer to have real, deep immutability, but `const` isn't it.

Yep, agree with you on that. That was one of the main points of my post.

> Because `const` requires to be initialized at the time of definition, you know the variable is valid for its entire lifetime.

No, that's where you're mistaken. All you really know is that the variable assignment is reliable. If the assignment happens to be of an immutable value, great.

But given the choice between these two guarantees:

1. The assignment is immutable

2. The value is immutable

(1) is almost pointless -- and a quick scan of your small block where it's scoped will verify that or not.

(2) is the absolute important one.

`const` masquerades as (2) when it's really only (1). That's what I don't like about it.


Your blog post doesn't make a very strong argument, honestly.


I would genuinely (no sarcasm) like to read a blog post that makes a stronger argument (in either direction) than mine. Most blog posts I've read about `const` cite very flimsy things like "it makes my code more readable" without any real meat as to why that's true. I tried to debunk that with careful thought. If someone has more convincing arguments besides subjective opinion, I'd love to read them.


>I tried to debunk that with careful thought.

Well you didn't really succeed. Your argument against using const comes down to "someone might violate expectations or remove the word const, so let's not even try". I don't agree with that.

Traditional constants are used to show that nothing changes in large swaths of code; showing how much benefit you get in a standalone 12 line function is not meaningful.

Also, calling function scoping bad is not revisionism. That is not a new idea, and does not depend on 'let' existing.

Edit: And the the article you linked about let is saying so many ridiculous things I won't even try to respond to it, but jeez. 'implicit'


> Your argument against using const comes down to ...

You did an absolutely horrible job of summarizing even remotely what my argument was, so I guess "I won't even try to respond to" this, since you either can't critically read or didn't care to this time. jeez.


That's what you devote several paragraphs to. I'm not sure how else you explain quotes like "the likelihood is that a developer who needs to change a variable and gets a const-thrown error about it will probably just change the const to let and go on about their business."

I don't care to argue about 'let' because this is a post about 'const'. I was writing a rebuttal in the previous comment but realized it was way too off-topic. But your definition and reasoning for "implicit scope" hurts me to read.


When you say "don't use [var]" do you mean only in ES6, or even before ES6?

If before ES6, then if you could explain your reasoning more I'd be interested to hear it. I use var regularly, but would be happy to be re-educated.


There is no alternative to var before ES6, so of course your only option is to use it. If you use any of the various compilers that introduce let and const, it is recommended to use them over var. These compilers basically just check, at compile time, those variables satisfy the correct behaviors of let and const and just converts them to var for ES5 and below.


Why not use `const` as default? I would argue that using `let` instead is a premature optimization of readability.

It's easier to reason about a variable if you know it will always be one value—even if the value may be mutated (which is unfortunate).

`const` is a simpler construct. It has less rules and I would always suggest that using `let` will introduce complexity that only needs to be there when you can identify a genuine need to re-assign a value to your variable.

That said, I enjoyed the post. It's a good read!


  > It's easier to reason about a variable if you know it will always be one value—even
  > if the value may be mutated (which is unfortunate).
True, but because const, let, and var are lexically-scoped, it’s super-easy to reason about everything that happens to variables. (Excepting bad JS environments where you can declare global constants, of course).

If I see PI = 3.14159265 somewhere in a file, it’s not a hardship to assume it will not be rebound, and to notice if it is rebound.

The super-important thing to reason about is value mutability, because that escapes lexical scope. We pass mutable values to library functions containing code we have never read. We integrate with code written by a colleague who is in another time zone and may accidentally change a routine to mutate an array we pass with .push instead of .concat.

“Easier to reason about” is massively important with mutability. Not so much with lexical bindings.


Totally agree, it is much more important to be able to reason about mutability.

I would disagree that it's super-easy to reason about var, despite being lexically scoped. It still causes an enormous amount of confusion when loops and callbacks are used together.

The PI example only really works because we all know what PI should look like. If you were using someone else's constant value and it accidentally got rebound it would be much harder to know.


Fair enough about var, it is annoying in ways that are orthogonal to rebindability (is that a word?)

Also, here’s my question: If I can write:

  const PI = 3.14159265;
Why can’t I write:

  const function diameter (r) { return 2 * PI * r; }
???


Probably for the same reasons you can't write

    var function diameter() {}
It's definitely a shame that the only way to achieve consistent behaviour between variables and named functions requires you to write the name twice.

    const diameter = function diameter() {};
If it wasn't for the usefulness of named functions for debugging and hoisting, I would just use:

    const diameter = function() {};


Except that any variation of these doesn't produce the same thing as a declaration, since function declarations hoist and function expression assignments do not.

Most people don't like hoisting -- and I agree that variable hoisting is crazy. I'd never advocate this:

    a = 2;
    var a;
But function hoisting I find super useful. I always prefer to write my files like this:

    foo();
    bar();
    
    // ....
    
    function foo() { .. }
    
    function bar() { .. }
...because when I open a file, I don't want to go hunting for any executable code, I just want to look at the very top. Function declaration hoisting lets me put those definitions at the bottom and the code that uses them at the top.

I've found this to be much more useful in code maintenance.


I agree on function declarations even though I avoid hoisting in other situations. I don't see any serious downsides to using function declarations (unless within a function, perhaps), and I find it easier to read, especially in ES6: `export function diameter` vs. `export const diameter = ...`.


If you just write everything in order of usage, the executable code still winds up in a very easy-to-find place -- the very bottom, rather than the top.


That's how I write my JS as well. It's like speaking


Actually, I believe arrow functions keep the name of the binding.

    const diameter = () => {};
    export const radius = () => {};
Could be wrong.


What you're thinking about in ES6 is called "function name inference" and only refers to certain forms, like this one, where the name -- PURELY FOR DEBUGGING -- can be inferred. Actually, it goes for all assignments, `var`, `let`, and `const`.

Otherwise, the arrow itself is anonymous and thus you don't have a name to make an inner self-reference with.


What you have written is a function statement rather than a function expression.

This is possible, but has no utility beyond the original.

    const diameter = function diameter (r) { return 2 * PI * r; }


As other comments have noted, I a speaking to the fact that you can’t make a function declaration while also specifying that the name is not to be re-bound within its scope.

A function expression bound to a constant block-scoped variable does not have the exact same semantics.


What is the goal of marking that function const? Is it to prevent redefining the function, or as a hint that it is a pure function and it's outputs can be memoized?


Yeah, see, this is exactly the problem. I think most developers assume the latter, when the former is all that's meant according to JS.

I don't think the former is all that useful, and the latter is super useful but `const` doesn't do anything to help with that.


The fact that you can redefine a function gives me a terribly unsettled feeling, probably because I remember the assembly language days when we were told not to write self-modifying code, as it's not re-entrant. (If JS were ever in a concurrent environment, would re-defining functions lead to problems?)


> Why not use `const` as default?

The argument I made toward the end of the post is that refactoring from `const` back to `let` is more risky than the other way around.

Moreover, surveying my own code, honestly, the vast majority of my declarations should be `var` or `let`, and only a much smaller amount being `const`. I think a lot of people are rushing blindly to `const` without realizing its full behavior.

> [`const`] has less rules ... using `let` will introduce complexity

I think this is completely backwards. `let` has no special caveats (other than the TDZ thing, which they both have), whereas `const` has this caveat that it's only about assignment bindings and not about values.

Imagine a piece of code that has `const x = 2` that you later refactor to `const x = [2]`. The complexity is, you now have to contend with the `[2]` being a mutable value where `2` wasn't, and `const` sorta pretends to keep you safe but doesn't.

That's the complexity I disfavor.


`const` has this caveat that it's only about assignment bindings and not about values.

Imagine a piece of code that has `const x = 2` that you later refactor to `const x = [2]`. The complexity is, you now have to contend with the `[2]` being a mutable value where `2` wasn't, and `const` sorta pretends to keep you safe but doesn't.

If these semantics aren't instantly obvious to you, then that's emblematic of a serious gap in your understanding of programming. I say this not with the intent of shaming, but to recommend that you (and the very many other people with the same lapse) invest some time into developing intuition about reference semantics. The best way IMO is to get initmately familiar with a language that has first class pointers or otherwise explicit indirection. Learn C, get good at C, write something big in C. Focus on the pointer semantics and you'll come out a significantly better programmer in languages where pointers are slightly less immediately visible.

Of course if you're making this recommendation because you work with other people who have a lapse in their understanding of references I can understand that. I would encourage you, however, to try to fix the problem rather than work around it by stunting the language. After all, it's an issue that will crop up everywhere, not just the semantics of `const'.


Oh, I see. I just need to learn more. The problem is I just don't know JS (or other langs for that matter).

http://YouDontKnowJS.com


I agree that saying this issue is "emblematic of a serious gap in your understanding of programming" is.. well.. douchebaggery.

However I feel a lot of the article and many of your posts about it in here come down to "it makes me feel uncomfortable" which.. I mean, ok. Sure. I hate spaces for indentation and think double quotes are better (and I have reasons for both)... but I don't say there's no use to the other options.

I feel you're throwing the baby out with the bathwater here.

Should const probably have been about value immutability too? Yes, I think so. I totally agree. That's a REALLY important topic to discuss. Maybe pitch it to the ES2016 committee as an iterative upgrade to const?

But as it stands it does have value. Yes it mostly just codifies what programmers original did as a language construct but it does also stop redefinitions which was an annoyingly common issue. So it's progress. Small, incremental, and not as full-featured as we'd both like... but it's progress.

Drawing attention to the issues with const is important... but there's no reason to rail so hard against its usage when it's understood to have those limitations... is there?


But my post did actually conclude the cases where it's useful. It seems like a lot of people here only read the first half of the post and stopped.

I don't think `const` should be removed. I just don't think we should lead with it. I'm not sure why that's not coming across except for the fact that the post isn't being read.


Hmmm I think maybe your tone comes across stronger than you mean it then.

Because, while you do say that, it comes across as a sort of "meh, it's not totally useless but I still hate it" kind of thing at the end.

Especially as you spent the last... like 2000 words trashing it.


Using the mantra code is communication, declaring everything as const lets others (and your future self) know that as of last writing they can expect this variable to not be reassigned. I think that small piece of information can be useful.

To your point however, `const` does not imply constant in the sense of other languages (like `.freeze()` does) so it does mean your target future audience is someone who is familiar with some of the nuances of recent JavaScript, an assumption that can be dangerous to make.

For my recent ES6 escapades I've been using the pattern of `const` first and `let` only when mutability of assignment is needed and I have yet to have that bite me in the ass. However this has been small projects and I can't speak for a larger codebase.


> To your point however, `const` does not imply constant in the sense of other languages (like `.freeze()` does) so it does mean your target future audience is someone who is familiar with some of the nuances of recent JavaScript, an assumption that can be dangerous to make.

Does any other language than Rust implement it this way? I know Rust needs to, for the borrowing semantics.

Java does it the same way as JS. Scala and Haskell both make everything immutable by default as convention, but there's nothing preventing you from mutating a `var` field of a `val` reference, or abusing `unsafePerformIO` to modify an `IORef` from a supposedly pure context.


C++

Assume you have a class Foo, and this:

const Foo fooInst(1, 2, 3);

if you try to call a method foo.bar() you can't, unless that method bar() is also marked const with this type of signature:

int bar() const;

Meaning that bar returns an int, but can't assign to any fields or call any other non-const methods.

Although, it does have this hideous const_cast<> operator that anyone can use to break these rules.


I think that completely depends on the flavour of code you write. If you mostly use imperative styles, then the large proportion of your declarations will be `var` and `let`. It definitely makes refactoring harder in the direction you mentioned.

Alternatively, for a functional style, you have to have a fairly good reason for using `var` or `let`. I find myself treating them like I'd treat atoms in Clojure. They end up being the mechanism you use to make controlled side-effects.

I'll concede that `const` does have a complexity to understand...initially. However once you understand the distinction between assignment and value (you only have to understand it once), then you're back to a level playing field between both.

From then on, `const` gives you a guarantee about the assignment. Objectively, this guarantee eliminates complexity, you can look at a variable anywhere in a scope and know that it has the same value as at time of declaration (same TDZ exceptions apply). I don't see how this could be seen as backwards.


const conveys additional information. It says "this name will not be rebound," not just to the reader, but to the machine interpreting this as well.

As a reader, I know that after reading a const line I don't have to check if someone rebinds this name before each use site -- it's impossible.

Interpreters and static analysis tools use this information to stop you when you write a piece of code that rebinds the name, asking "did you really mean to do that? You said you wouldn't."


> I know that after reading a const line I don't have to check if someone rebinds this name before each use site -- it's impossible.

This turns out to be almost useless information for most of us, since the real problem is not re-assignment, but mutation of the value (in the case of non-primitives).

`const x = 2` feels great emotionally, but `const x = [2]` has the feeling of safety without any of the guarantee.

This is a classic case (quoted from Crockford regularly) of a utility that it sometimes useful and sometimes harmful (aka not useful), and there's a better option (`let`), so the better option should generally be preferred.

I only use `const` (refactor from `let`) when a piece of code is reasonably complete and I'm pretty sure re-assignment will not ever be appropriate. Guessing at that before writing the code is a premature optimization.

And guessing wrongly and having to refactor back to `let` later is more risky.


> `const x = 2` feels great emotionally, but `const x = [2]` has the feeling of safety without any of the guarantee.

Const is perfectly sensible with references to mutable objects too. The latter guarantees that x will always point to the same mutable array. If you pass a reference to the x array to a function which needs to mutate that specific array or observe it for changes, then it may be important that x is never rebound to a different array. Consider the following code:

    const x = [5,6,7];

    Object.observe(x, function(changes) {
      console.log('changes', changes);
    });

    // ...
    // time to clear the array

    // WRONG! The code observing changes to the old x array will not see
    // this change or future changes to this new array. Because we used const, this
    // will trigger an error and immediately show us our mistake.
    x = [];

    // CORRECT. This mutates the array instead of creating a new empty array.
    x.length = 0;


> If you pass a reference to the x array to a function

This notion (concern) is meaningless in JS, since when you pass any reference, it's always a reference-copy, so there's no value nor assistance that `const` provides.


Const usage prevents a bug in my example directly above!


To be honest, I've been following the rule of using capital letters for things that should remain 'constant'. If everyone working on the project knows that, it works like a charm, never had any issues with it. But it's nice to have const now. So, I do see value in the const feature even if it doesn't protect me against a commit by the devil himself who changes the content of const objects.

Long story short: If you want to be annoyed about this feature, then more power to you. I won't be.


> I think the likelihood is that a developer who needs to change a variable and gets a const-thrown error about it will probably just change the const to let and go on about their business.

If this is the core argument to not using 'const', then you are out of luck trying to enforce any coding style. I might as well just delete any code that doesn't make sense.


It's not the core argument. It was a tangential side note in the blog post.


One advantage const can bring (at least when used with primitives or frozen values) can come from JS IDEs: if they detect a value is const, they can report its value when hovering the variable name, in a static way.

That's what Java IDEs do with static final variables.


> To most developers, it means that your LUCKY_NUMBERS will always be 12, 19, and 42.

Is that really true? I would assume the opposite. Most developers have used Java or C# or any of the other languages that have similar reference semantics. I would agree if you qualified that with "beginner".


I suspect if you did a survey, there would be plenty of programmers who would reply "it works like this, except these languages do something different", because "const means immutability" feels "right" despite many mainstay languages doing it differently.


And people complain const in C is too complicated...


How do you mean? All this post is saying is that “const” in JavaScript is the same as “final” in Java—it lets you make immutable references, but not immutable objects. In C terms, you can express this:

    T *const x = ...;  // Immutable pointer to mutable T.
But not this:

    T const *x = ...;  // Mutable pointer to immutable T.
Even so, I don’t agree with the author’s argument that “const” is to be avoided. It does offer some protection against minor errors, which is not nothing.


I don't think my argument was to avoid `const`. I think my argument was to pick `const` at the end rather than at the beginning. I feel those are different arguments.


To be clear, “choose X last” does give the impression of “avoid X”, by which I don’t mean “don’t use X at all”. I disagree, but it’s a small matter; it’s not as if it would bother me to read your code.


disclosure: self submission




Join us for AI Startup School this June 16-17 in San Francisco!

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

Search: