Live data from Hacker News

John Carmack on mutable variables

twitter.com

571–580 of 663 posts

Re: John Carmack on mutable variables

#571
post #185

Earlier quoted context omitted.

The way I like to think about is that with immutable data as default and pure functions, you get to treat the pure functions as black boxes. You don't need to know what's going on inside, and the function doesn't need to know what's going on in the outside world. The data shape becomes the contract. As such, localized context, everywhere, is perhaps the best way to explain it from the point of view of a mutable world…

Sure modularity, encapsulation etc are great tools for making components understandable and maintainable. However, don't you still need to understand the entire program as ultimately that's what you are trying to build. And if the state of the entire programme doesn't change - then nothing has happened. ie there still has to be mutable state somewhere - so where is it moved to?

You are very right in that things need to change. If they don't, nothing interesting happens and we as programmers don't get paid :p. State changes are typically moved to the edges of a program. Functional Core, Imperative Shell is the name for that particular architecture style.

FCIS can be summed up as: R->L->W where R are all your reads, L is where all the logic happens and is done in the FP paradigm, and W are all your writes. Do all the Reads at the start, handle the Logic in the middle, Write at the end when all the results have been computed. Teasing these things apart can be a real pain to do, but the payoff can be quite significant. You can test all your logic without needing database or other services up and running. The logic in the middle becomes less brittle and allows for easier refactoring as there is a clear separation between R, L and W.

For your first question. Yes, and I might misunderstand the question, so give me some rope to hang myself with will ya ;). I would argue that what you really need to care about is the data that you are working with. That's the real program. Data comes in, you do some type of transformation of that data, and you write it somewhere in order to produce an effect (the interesting part). The part where FP becomes really powerful, is when you have data that always has a certain shape, and all your functions understands and can work with the shape of that data. When that happens, the functions starts to behave more like lego blocks. The data shape is the contract between the functions, and as long as they keep to that contract, you can switch out functions as needed. And so, in order to answer the question, yes, you do need to understand the entire program, but only as the programmer. The function doesn't, and that's the point. When the code that resides in the function doesn't need to worry about what the state of the rest of the program is, you as the programmer can reason about the logic inside, without having to worry about some other part of the program doing something that it should do that at the same time will mess up the code that is inside the function.

Debugging in FP typically involves knowing the data and the function that was called. You rarely need to know the entire state of the program.

Does it make sense?

Re: John Carmack on mutable variables

#572

Earlier quoted context omitted.

In the vast majority of cases, developer ergonomics are much more important than freeing memory a little earlier. In other scenarios, e.g., when dealing with large data frames, the memory management argument carries more weight. Though even then there are usually better patterns, like method chaining. FYI John Carmack is a true legend in the field. Despite his not being a lifelong Python guy, I can assure you he is s…

>developer ergonomics are much more important than freeing memory a little earlier Preach to the python choir bro, but it should be telling when a python bro considers it's too ergonomic and wasteful. At some point being clean and efficient about the code is actually ergonomic, no one wants to write sloppy code that overallocates, doesn't free, and does useless work. To quote Steve Jobs, even if no one sees the insid…

In this case, overuse of re-assigning is the sloppy thing to do, and immutability by default is the craftsman's move. Reducing your program's memory footprint by re-assigning variables all the time is a false economy.

Re: John Carmack on mutable variables

#573

Earlier quoted context omitted.

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 woul…

I did this accidentally the other day in Rust:

let x = some_function();

... A bunch of code

let x = some_function();

The values of x are the same. It was just an oversight on my part but wondered if I could set my linter to highlight multiple uses of the same variable name in the same function. Does anyone have any suggestions?

Re: John Carmack on mutable variables

#574

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?

I try to avoid this ambiguity by calling such variables "values".

It leads to a further ambiguity, because "value" is something that is assigned to a variable (or whatever we call it). For example some Rust code:

  let v1 = Vec::new();
  let v2 = v1;
The first line creates value of type Vec and places it into variable v1. The second line moves the value from variable v1 to variable v2. If we rename "variable" to "value" then my description of the code will become unreadable.

If I was as pedantic as the OP, I'd use "lexical binding" instead of "variable". But I'm not sure how it will work with C and C++, because their semantics assumes that a variable has a memory associated with it that can hold a value of a given type. Modern compilers are smarter than that, but still they try hard to preserve the original semantics. The variable in C/C++ is not just a name that ceases to exist after compiler have done its work. It creates a possibility that calling C/C++ variables "lexical bindings" we'll get more pedants accusing us of improper use of words, even if we never change values of those variables.

Re: John Carmack on mutable variables

#575
post #400

Earlier quoted context omitted.

If you read through the Big Red Book¹ or its counterpart for Kotlin², it's quite explicit about the goals with these techniques for managing effects, and goes over rewriting imperative code to manage state in a "pure" way. I think the authors are quite aware of the relationship between these techniques and mutable state! I imagine it's similar for other canonical functional programming texts. Besides the "pure" funct…

> If you read through the Big Red Book This is a thoughtful response, but I can't help but chuckle at a response that starts with, just read this book! .

For me, well-written books are an enjoyable way to learn, and I'll admit I'm partial to that!

But of course you can learn in whatever way you like. Books are just a convenient example to point to as an indicator of how implementers, enthusiasts, and educators working with these techniques make sense of them and compare them to mutating variables. They're easy to refer to because they're notable public artifacts.

Fwiw, there's also an audiobook of the Red Book. To really follow the important parts, you'll want to be reading and writing and running code, but you can definitely get a sense of the more basic philosophical orientation just listening along while doing chores or whatever. :)

Re: John Carmack on mutable variables

#576

> I wish it was the default, and mutable was a keyword. I wish the IDE would simply provide a small clue, visible but graphically unobtrusive, that it was mutated . In fact, I end up wishing this about almost every language feature that passes my mind. For example, I don't need to choose whether I can or can't append to a list; just make it unappendable if you can prove I don't append. I don't care if it's a map, lis…

Could Pylint help? It has atleast check for variable redefinition: https://pylint.pycqa.org/en/latest/user_guide/messages/refac...

For type only though.

Re: John Carmack on mutable variables

#577

Earlier quoted context omitted.

Lenses is mutation by another name. You are basically recreating states on top of an immutable system. Sure, it's all immutable actually but conceptually it doesn't really change anything. That's what makes it hilarious. In the end, the world is stateful and even the purest abstractions have to hit the road at some point. But the authors of Haskell were fully aware of that. The monadic type system was conceived as a…

But there isn’t anything hilarious about that. It’s a clear-minded and deliberate approach to reconciling principle with pragmatic utility. We can debate whether it’s the best approach, but it isn’t like… logically inconsistent, surprising, or lacking in self awareness.

You might also think of it a bit like poetry: creativity emerging from the process of working within formal constraints. By asking how you can represent something familiar in a specially structured way, you can learn both about that structure and the thing you're trying to unite with it. Occasionally, you'll even create something beautiful or powerful, as well.

Maybe in that sense there's an "artificial" challenge involved, but it's artificial in the sense of being deliberate rather than merely arbitrary or absurd.

Re: John Carmack on mutable variables

#578
post #188

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.

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…

Guys, guys, I don't think we're on the same page here.

The conversation I'm trying to have is "stop mutating all the dynamic self-modifying code, it's jamming things up". The concept of non-mutating code, only mutating variables, strikes me as extremely OCD and overly bureaucratic. Baby steps. Eventually I'll transition from my dynamic recompilation self-modifying code to just regular code with modifying variables. Only then can we talk about higher level transcendental OOP things such as singleton factory model-view-controller-singleton-const-factories and facade messenger const variable type design patterns. Surely those people are well reasoned and not fanatics like me

Re: John Carmack on mutable variables

#579

Earlier quoted context omitted.

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 woul…

In Rust, one way I use shadowing is to gather a bunch of examples into one function, but you can copy and paste any single example and it would work.

    fn do_demo() {
        let qr = QrCode::encode_text("foobar");
        print_qr(qr);
        
        let qr = QrCode::encode_text("1234", Ecc::LOW);
        print_qr(qr);
        
        let qr = QrCode::encode_text("the quick brown fox");
        print_qr(qr);
    }
In other languages that don't allow shadowing (e.g. C, Java), the first example would declare the variable and be syntactically correct to copy out, but the subsequent examples would cause a syntax error when copied out.

Re: John Carmack on mutable variables

#580

Earlier quoted context omitted.

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).

That's not mutation though. The `assoc` on the second binding is returning a new object; you're just shadowing the previous binding name. This is different than mutation, because if you were to introduce an intermediate binding here, or break this into two `let`s, you could be holding references to both objects {:a 1} and {:a 1 :b 2} at any time in a consistent way - including in a future/promise dereferenced later.

regardless of the mechanism, you still run into the exact same problem John had.
Post reply on HN