> 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.
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.
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; }
??? 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() {};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.
const diameter = () => {};
export const radius = () => {};
Could be wrong.Otherwise, the arrow itself is anonymous and thus you don't have a name to make an inner self-reference with.
This is possible, but has no utility beyond the original.
const diameter = function diameter (r) { return 2 * PI * r; }A function expression bound to a constant block-scoped variable does not have the exact same semantics.
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 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.
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'.
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?
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.
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.
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.
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.
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.
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.
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!