Live data from Hacker News

John Carmack on mutable variables

twitter.com

421–430 of 663 posts

Re: John Carmack on mutable variables

#421
post #385

Earlier quoted context omitted.

Or between iterations of the loop scope in which it's defined, const/immutable definitions absolutely change during the execution of a function. I understand the nitpicky argument, I just think it's kinda dumb. It's a transparent attempt to justify jargon that we all know is needlessly confusing.

Ah! Actually this idea that the immutable variables in a loop "change during execution" is a serious misunderstanding and some languages have tripped themselves up and had to fix it later when they baked this mistake into the language. What's happening is that each iteration of the loop these are new variables but they have the same name, they're not the same variables with a different value. When a language designer…

Seems like you're coming around to my side of the fence that calling these clearly distinct constant expressions "variables" is probably a mistake?

Re: John Carmack on mutable variables

#422
post #188

Earlier quoted context omitted.

I think the explanation is: When you mutate variables it implicitly creates an ordering dependency - later uses of the variable rely on previous mutations. However, this is an implicit dependency that isn't modeled by the language so reordering won't cause any errors. With a very basic concrete example: x = 7 x = x + 3 x = x / 2 Vs x = 7 x1 = x + 3 x2 = x1 / 2 Reordering the first will have no error, but you'll get t…

Yet even Rust allows you to shadow variables with another one with the same name. Yes, they are two different variables, but for a human reader they have the same name. I think that Rust made this decision because the x1, x2, x3 style of code is really a pain in the ass to write.

I do find shadowing useful. If you're writing really long code blocks in which it becomes an issue, you are probably doing too much in one place.

Re: John Carmack on mutable variables

#423

After a 2 year Clojure stint I find it very hard to explain the clarity that comes with immutability for programmers used to trigger effects with a mutation. I think it may be one of those things you have to see in order to understand.

Carmack is talking about variable reassignment here, which Clojure will happily let you mutate. For example: (let [result {:a 1} result (assoc result :b 2)] ...) He mentions that C and C++ allow const variables, but Clojure doesn't support that. clj-kondo has a :shadowed-var rule, but it will only find cases where you shadow a top-level var (not the case in my example).

It's more nuanced, because the shadowing is block-local, so when the lexical scope exits the prior bindings are restored.

I think in practice this is the ideal middle ground of convenience (putting version numbers at the end of variables being annoying), but retaining mostly sane semantics and reuse of prior intermediate results.

Re: John Carmack on mutable variables

#424

I try to keep deeper mutation to where it belongs, but I'll admit to shadowing variables pretty often. If I have a `result` and I need to post-process it. I'm generally much happier doing `result = result.process()` rather than having something like `preresult`. Works nicely in cases where you end up moving it into a condition, or commenting it out to test an assumption while developing. If there's an obvious name fo…

Yes, but there are often FP tricks and conveniences that make this unnecessary. Like chaining or composing function calls. result = x |> foo |> bar |> baz (-> x foo bar baz) Or map and reduce for iterating over collections. Etc.

Yea, very true. Not every language makes this nice though.

Re: John Carmack on mutable variables

#425

I completely agree with the assertion and the benefits that ensue, but my attention is always snagged by the nomenclature. I know there are alternate names available to us, but even in the context of this very conversation (and headline), the thing is being called a "variable." What is a "variable" if not something that varies?

In the cases we're interested in here the variable does vary, what it doesn't do is mutate. Suppose I have a function which sums up all the prices of products in a cart, the total so far will frequently mutate, that's fine. In Rust we need to mark this variable "mut" because it will be mutated as each product's price is added. After calculating this total, we also add $10 shipping charge. That's a constant, we're (fo…

Even if the term 'variable' has roots in math where it is acceptable that it might not mutate, I think for clarity, the naming should be different. It's uneasy to think about something that can vary but not mutate. More clear names can be found.

Re: John Carmack on mutable variables

#426
post #188

Earlier quoted context omitted.

I think the explanation is: When you mutate variables it implicitly creates an ordering dependency - later uses of the variable rely on previous mutations. However, this is an implicit dependency that isn't modeled by the language so reordering won't cause any errors. With a very basic concrete example: x = 7 x = x + 3 x = x / 2 Vs x = 7 x1 = x + 3 x2 = x1 / 2 Reordering the first will have no error, but you'll get t…

Yet even Rust allows you to shadow variables with another one with the same name. Yes, they are two different variables, but for a human reader they have the same name. I think that Rust made this decision because the x1, x2, x3 style of code is really a pain in the ass to write.

In idiomatic Rust you usually shadow variables with another one of the same name when the type is the only thing meaningfully changing. For example

   let x = "29"
   let x = x.parse::()
   let x = x.unwrap()
These all use the same name, but you still have the same explicit ordering dependency because they are typed differently. The first is a &str, the second a Result, the third an i32, and any reordering of the lines would provide a compiler error. And if you add another line `let y = process(x)` you would expect it to do something similar no matter where you introduce it in these statements, provided it accepts the current type of x, because the values represent the "same" data.

Once you actually "change" the value, for example by dividing by 3, I would consider it unidiomatic to shadow under the same name. Either mark it as mutable for preferably make a new variable with a name that represents what the new value now expresses

Re: John Carmack on mutable variables

#427
post #404

Earlier quoted context omitted.

It also doesn't make sense for `process()` to be an attribute of `result`. Why would you instantiate a class and call it result‽

A more common example for me at work is getting a response from url. Then you gotta process it further like response.json() or response.header or response.text etc etc. and then again select the necessary array index or doc value from it. Giving a name like pre_result or result_json etc etc would just become cumbersome.

I would never do `response = response.json()`. I use it when it's effectively the same type, but with further processing which may be optional.

Re: John Carmack on mutable variables

#428

I completely agree with the assertion and the benefits that ensue, but my attention is always snagged by the nomenclature. I know there are alternate names available to us, but even in the context of this very conversation (and headline), the thing is being called a "variable." What is a "variable" if not something that varies?

That's why in some languages they don't call them variables, but bindings instead.

(let [a 10] a)

Let the symbol `a` be bound to the value `10` in the enclosing scope.

Re: John Carmack on mutable variables

#429
post #188

Earlier quoted context omitted.

I think the explanation is: When you mutate variables it implicitly creates an ordering dependency - later uses of the variable rely on previous mutations. However, this is an implicit dependency that isn't modeled by the language so reordering won't cause any errors. With a very basic concrete example: x = 7 x = x + 3 x = x / 2 Vs x = 7 x1 = x + 3 x2 = x1 / 2 Reordering the first will have no error, but you'll get t…

whats the difference between immutable and constant, which has been in use far longer? why are you calling it mutable?

In plenty of languages, there's not really a difference. In Rust, there is a difference between a `let var_name = 10;` and `const var_name: u64 = 10;` in that the latter must have its value known at compile-time (it's a true constant).

> why are you calling it mutable?

Mostly just convention. Rust has immutable by default and you have to mark variables specifically with `mut` (so `let mut var_name = 10;`). Other languages distinguish between variables and values, so var and val, or something like that. Or they might do var and const (JS does this I think) to be more distinct.

Re: John Carmack on mutable variables

#430

I try to keep deeper mutation to where it belongs, but I'll admit to shadowing variables pretty often. If I have a `result` and I need to post-process it. I'm generally much happier doing `result = result.process()` rather than having something like `preresult`. Works nicely in cases where you end up moving it into a condition, or commenting it out to test an assumption while developing. If there's an obvious name fo…

You're using really generic terms which I have to think is mostly because you're talking about it in the abstract. In most scenarios I find there are obvious non-generic names I can use for each step of a calculation.

I disagree you'd find "obvious" non-generic names easily. After all, "naming" is one of the hardest things in computer science.
Post reply on HN