[HN Gopher] John Carmack on mutable variables
       ___________________________________________________________________
        
       John Carmack on mutable variables
        
       Author : azhenley
       Score  : 455 points
       Date   : 2025-10-31 02:34 UTC (20 hours ago)
        
 (HTM) web link (twitter.com)
 (TXT) w3m dump (twitter.com)
        
       | munchler wrote:
       | > Making almost every variable const at initialization is good
       | practice. I wish it was the default, and mutable was a keyword.
       | 
       | It's funny how functional programming is slowly becoming the best
       | practice for modern code (pure functions, no side-effects), yet
       | functional programming _languages_ are still considered fringe
       | tech for some reason.
       | 
       | If you want a language where const is the default and mutable is
       | a keyword, try F# for starters. I switched and never looked back.
        
         | Terr_ wrote:
         | While it's amusing, I think it's sensible: One of the main
         | tasks in most businessy programming is to take what a human
         | wants, translate it to code, reverse-translate it _back_ to
         | human understanding later, modify it, and translate it again to
         | slightly different code.
         | 
         | This creates friction between casual stakeholder models of a
         | mutable world, versus the the assumptions an immutable/mostly-
         | pure language imposes. When the customer describes what they
         | need, that might be pretty close to a plain loop with a
         | variable that increments sometimes and which can terminate
         | early. In contrast, it maps less-cleanly to a pure-functional
         | world, if I'm lucky there will at least be a reduce-while
         | utility function, so I don't need to make all my own recursive
         | plumbing.
         | 
         | So immutability and pure-functions are like locking down a
         | design or optimizing--it's not great if you're doing it
         | prematurely. I think that's why starting with mutable stuff and
         | then selectively immutable'ing things is popular.
         | 
         | Come to think of it, something similar can be said about
         | weak/strong typing. However the impact of having too-strong
         | typing seems easier to handle with refactoring tools, versus
         | the problem of being too-functional.
        
         | Freedom2 wrote:
         | Decades of blog posts, articles and books about "best
         | practices" led to a suspicion of anything that wasn't bog
         | standard OOP. This site in particular has also contributed to
         | that, especially in the early 2000s, where you can easily find
         | comments disparaging FP.
        
           | jeffreygoesto wrote:
           | I think there's a deeper mechanism in these discussions. They
           | all tend to go "If you do <insert_arbitrary_method_here>
           | fully correct[0], it solves all of your problems."
           | 
           | But [0] is never possible.
        
         | josephg wrote:
         | > If you want a language where const is the default and mutable
         | is a keyword, try F# for starters. I switched and never looked
         | back.
         | 
         | Rust is also like this (let x = 5; / let mut x = 5;).
         | 
         | Or you can also use javascript, typescript and zig like this.
         | Just default to declaring variables with const instead of let /
         | var.
         | 
         | Or swift, which has let (const) vs var (mutable).
         | 
         | FP got there first, but you don't need to use F# to have
         | variables default to constants. Just use almost any language
         | newer than C++.
        
           | bathtub365 wrote:
           | Are there languages that automatically extend this to things
           | like data structure members? One of the things I like about
           | the C++ const keyword is that if you declare an instance of a
           | struct/class as const it extends that to its members. If the
           | instance isn't const, you can still mutate them (as long as
           | they aren't declared const within the structure itself)
        
             | orlp wrote:
             | Rust works this way, yes. There are escape hatches though,
             | which allow interior mutability.
        
               | snalty wrote:
               | Yeah, here's an example: https://play.rust-
               | lang.org/?version=stable&mode=debug&editio...
        
             | umanwizard wrote:
             | If I understand what you're asking correctly, rust is also
             | like this. If you have a non-mut value of (or non-mut
             | reference to) an object, you only get non-mut access to its
             | members.
        
           | Fire-Dragon-DoL wrote:
           | In typescript and js you get immutable references, but the
           | data is mutable. Definitely not the same thing
        
             | dougherty wrote:
             | You have `as const`. Yes I know it's not enforced at
             | runtime, but the type system does support it.
        
               | danenania wrote:
               | There's Object.freeze for enforcing at runtime.
        
           | eitland wrote:
           | In Java you can use final[1]. And yes, if final points to an
           | ArrayList you can change it, but you can also use final
           | together with immutable data structures[2].
           | 
           | [1]: https://www.baeldung.com/java-final
           | 
           | [2]: https://www.baeldung.com/java-immutable-list
        
             | haspok wrote:
             | Did you know that "final" does not actually mean final in
             | Java (as in: the variable can be constant folded)? Reasons
             | include reflection and serialization (the feature that
             | nowadays nobody uses, but due to backwards compatibility
             | the Java language developers always have to worry about?).
             | There was an excellent talk about this recently, I think
             | triggered by a new JEP "stable values":
             | https://youtu.be/FLXaRJaWlu4
        
         | ehsankia wrote:
         | functional programming has a lot of wonderful concepts, which
         | are very interesting in theory, but in practice, the strictness
         | of it edges on annoying and greatly hurts velocity.
         | 
         | Python has a lot of functional-like patterns and constructs,
         | but it's not a pure functional language. Similarly, Python
         | these days allow you to adds as much type information as you
         | want which can provide you a ton of static checks, but it's not
         | forced you like other typed languages. If some random private
         | function is too messy to annotate and not worth it, you can
         | just skip it.
         | 
         | I like the flexibility, since it leads to velocity and also
         | just straight up more enjoyable.
        
         | tasn wrote:
         | Functional programming languages (almost always?) come with the
         | baggage of foreign looking syntax. Additionally, imperative is
         | easier in some situations, so having that escape hatch is
         | great.
         | 
         | I think that's why we're seeing a lot of what you're
         | describing. E.g. with Rust you end up writing mostly functional
         | code with a bit of imperative mixed in.
         | 
         | Additional, most software is not pure (human input, disk,
         | network, etc), so a pure first approach ends up being weird for
         | many people.
         | 
         | At least based on my experience.
        
           | rapind wrote:
           | > come with the baggage of foreign looking syntax
           | 
           | Maybe they're right about the syntax too though? :)
        
             | kragen wrote:
             | Which one, Erlang, Lisp, or ML?
        
               | nickpeterson wrote:
               | Honestly I find ML derived languages the most pleasant to
               | look at.
        
               | tmtvl wrote:
               | Lisp syntax is objectively superior because you can write
               | a predicate like thus:                 (< lower-bound
               | x-coordinate upper-bound)
               | 
               | Instead of having to do the awful ampersand dance.
        
               | kragen wrote:
               | In Python or Icon (or Unicon) that's
               | lower_bound < x_coordinate < upper_bound
               | 
               | except that usually you want
               | lower_bound <= x_coordinate < upper_bound
               | 
               | which is also legal.
               | 
               | In Python this is a magical special case, but in
               | Icon/Unicon it falls out somewhat naturally from the
               | language semantics; comparisons don't return Boolean
               | values, but rather fail (returning no value) or succeed
               | (returning a usually unused value which is chosen to be,
               | IIRC, the right-hand operand).
               | 
               | And in SQL you have                   `x-coordinate`
               | between `lower-bound` - 1 and `upper-bound` + 1
               | 
               | which is obviously the best possible syntax.
        
               | rapind wrote:
               | ML superfan here. I don't mind lisp simplicity either.
               | Erlang is too alien for me, but maybe once I was used to
               | it.
        
               | impossiblefork wrote:
               | If you start programming in it though, syntax only
               | matters during the first day. Familiarity comes very
               | fast, and if you do five programming exercises, maybe one
               | a day, 'implement a hash map', 'make a small game', etc.
               | you will have no problems whatsoever once the week is
               | done.
               | 
               | If you have a course where one day you're supposed to do
               | Haskell and another Erlang, and another LISP, and another
               | Prolog, and there's only one exercise in each language,
               | then you're obviously going to have to waste a lot of
               | time on syntax, but that's never a situation you
               | encounter while actually programming.
        
               | rapind wrote:
               | I disagree, at least for my own case. I greatly prefer
               | reading ML code than C style syntax.
        
               | runevault wrote:
               | Eh, I'd say it depends.
               | 
               | I write way more algol-derived language code than ML, yet
               | piped operations being an option over either constant dot
               | operators where every function returns the answer or
               | itself depending on the context or inside out code is
               | beautiful in a way that I've never felt about my
               | C#/C++/etc code. And even Lisp can do that style with
               | arrow macros that exist in at least CL and Clojure (dunno
               | about Racket/other schemes).
        
               | kragen wrote:
               | This is far from true in my experience. I'm no Lisp hater
               | (I wrote a self-compiling compiler in Scheme) but
               | different syntaxes have dramatically different degrees of
               | familiarity, like Latin letters and Cyrillic. How long do
               | you think it would take you to learn to read phonetically
               | in Cyrillic as fast as you do in the Latin script? It
               | takes the humans months or years, if they ever arrive.
               | But, also, some notations are more usable than others,
               | even for experts, just as with other kinds of user
               | interfaces. Flat is better than nested, simple is better
               | than complex, complex is better than complicated, Perl
               | and MUMPS are unnecessarily error-prone, etc.
        
           | kragen wrote:
           | Rust is not very suitable for functional programming because
           | it is aggressively non-garbage-collected. Any time Rustaceans
           | want to do the kind of immutable DAG thing that gives
           | functional languages so much power, they seem to end up
           | either taking the huge performance and concurrency hit of
           | fine-grained reference counting, or they just stick all their
           | nodes in a big array.
        
             | tayo42 wrote:
             | Using a big array has good performance though?
        
               | kragen wrote:
               | Computer memory is already a big array. Probably what you
               | are thinking of is that processing array items
               | sequentially is a lot faster than following pointers, but
               | in the cases I'm talking about, you end up using array
               | indices instead of pointers, which isn't much faster.
        
               | gf000 wrote:
               | Yeah, but now logic bugs can cause memory leaks, doing
               | use-after-frees, etc, without _any_ kind of tooling to
               | prevent it (nothing like valgrind to catch them). Sure,
               | they won 't crash the program, but sending money from a
               | wrong account is _worse_ than a segfault, imo.
        
           | rao-v wrote:
           | Exactly this! I'd love a modern C++ like syntax with the
           | expressiveness of python and a mostly functional approach.
           | 
           | C# is not that far I suppose from what I want
        
             | tjk wrote:
             | Everybody's mileage will vary, but I find contemporary C#
             | to be an impressively well rounded language and ecosystem.
             | It's wonderfully boring, in the most positive sense of the
             | word.
        
               | LogicHound wrote:
               | I can't stand modern C#. They've bung in a bunch of new
               | keywords and features that are of dubious benefit every
               | release.
        
               | mrsmrtss wrote:
               | I'm interested what are those new keywords and features
               | that are of dubious benefit?
        
               | LogicHound wrote:
               | There is a huge amount of syntactic sugar that has been
               | added over the years that doesn't do whole lot IMO. It is
               | often imported from other languages (usually JavaScript
               | and/or Python).
               | 
               | e.g. Just a very simple example to illustrate the point
               | if (customer != null)         {
               | customer.Order = GetCurrentOrder();         }
               | 
               | vs                   if (customer is not null)         {
               | customer.Order = GetCurrentOrder();         }
               | 
               | Is there really any benefit in adding "is/is not"? I
               | would argue no. So I categorise that as being of "dubious
               | benefit" and there are many similar small features,
               | keywords etc. that get added each release where they
               | might save a few lines of code somewhere but I've never
               | seem them used that often.
        
               | mrsmrtss wrote:
               | In your sample there really is no benefit to using the
               | "is" operator over just checking for null (assuming you
               | haven't overloaded the "!=" operator). However, the "is"
               | operator is a lot more powerful, you can match an
               | expression against a pattern with it. Would you say that
               | these samples show no benefit to using the "is" operator?
               | 
               | if (obj is string s) { ... }
               | 
               | if (date is { Month: 10, Day: <=7, DayOfWeek:
               | DayOfWeek.Friday }) { ... }
               | 
               | https://learn.microsoft.com/en-us/dotnet/csharp/language-
               | ref... https://learn.microsoft.com/en-
               | us/dotnet/csharp/language-ref...
        
               | LogicHound wrote:
               | The issue is that I dislike the overall mentality of just
               | adding a bunch of language features. Things just seem to
               | be dumped in each release and I think to myself "When I
               | am going to use that?".
               | 
               | > Would you say that these samples show no benefit to
               | using the "is" operator?
               | 
               | I didn't say _no benefit_. I said _dubious benefit_.
               | 
               | I didn't really want to get into discussing specific
               | operators, but lets just use your date example:
               | if (date is { Month: 10, Day: <=7, DayOfWeek:
               | DayOfWeek.Friday }) { ... }
               | 
               | This following would do the same thing before the _is_
               | operator:                   static bool
               | IsFirstFridayOfOctober(DateTime date)         {
               | return date.Month == 10                 && date.Day <= 7
               | && date.DayOfWeek == DayOfWeek.Friday;         }
               | 
               | And then:                   if
               | IsFirstFridayOfOctober(date) {            ...         }
               | 
               | I understand it is more verbose. But do we really need a
               | new operator for this? I was getting on fine without it.
               | 
               | Each release there seems to be more of these language
               | features and half the time I have a hard time remembering
               | that they even exist.
               | 
               | Each time I meet with other .NET developers either
               | virtually or in Person they all seem to be salivating
               | over this stuff and I feel like I've walked in on some
               | sort of cult meeting.
        
               | mrsmrtss wrote:
               | I agree that they should not add new stuff lightly, but
               | the "is" operator actually should be looked together with
               | switch expression in the context of pattern matching. How
               | else could you enable powerful and succint pattern
               | matching in c#?
        
               | LogicHound wrote:
               | Arguments about whether the _is_ and _switch_ operators
               | should exist is missing the forest for the trees. I am
               | sure there are circumstances where it very useful.
               | 
               | It isn't any one language feature it is the mentality of
               | both the developer community and Microsoft.
               | 
               | > I agree that they should not add new stuff lightly
               | 
               | It seems though kinda do though. I am not the first
               | person to complain that they add syntactic sugar that
               | doesn't really benefit anything.
               | 
               | e.g. https://devclass.com/2024/04/26/new-c-12-feature-
               | proves-cont...
               | 
               | I have a raft of other complaints outside of language
               | features. Some of these are to do with the community
               | itself which only recognise something existing when
               | Microsoft has officially blessed it, it is like everyone
               | has received the official permission to talk about a
               | feature. Hot Reload was disabled in .NET 6 IIRC for
               | dubious reasons.
        
               | skeezyjefferson wrote:
               | > In your sample there really is no benefit to using the
               | "is" operator over just checking for null
               | 
               | Microsoft give the same example though. I understand what
               | hes saying, theres conceptual overlap between is and ==.
               | Many ways to do the same thing.
               | 
               | Why couldnt it just be...
               | 
               | if (obj == string s) { ... }
        
               | 1718627440 wrote:
               | In Python '==' and 'is' are not the same thing. '=='
               | checks for equality, 'is' for identity.
        
               | LogicHound wrote:
               | I am aware. I probably should have said "inspired".
        
             | dionian wrote:
             | Scala
        
           | gf000 wrote:
           | Foreign looking is just a person's biases/experience. C is
           | just as foreign looking to a layperson, you just happened to
           | start programming with C-family languages. (Also, as a
           | "gotcha" counterpoint, I can just look up a Haskell symbol in
           | hoogle, but the only language that needs a website to
           | decipher its gibberish type notation is C https://cdecl.org/
           | )
           | 
           | Nonetheless, I also heavily dislike non-alphabetical,
           | library-defined symbols (with the exception of math
           | operators), but this is a cheap argument and I don't think
           | this is the primary reason FPs are not more prevalent.
        
             | acdha wrote:
             | I think you could construct a stronger version of this
             | complaint as FP languages not prioritizing usability
             | enough. C was never seen as a great language but it was
             | relatively simple to learn to the point that you could have
             | running code doing something you needed without getting hit
             | with a ton of theory first. That code would probably be
             | unsafe in some way but the first impression of "I made a
             | machine do something useful!" carries a lot of weight and
             | anyone not starting with an academic CS background would
             | get that rush faster with most mainstream languages.
        
           | theSIRius wrote:
           | > Functional programming languages (almost always?) come with
           | the baggage of foreign looking syntax.
           | 
           | At least for me, this was solved by Gleam. The syntax is
           | pretty compact, and, from my experience, the language is
           | easily readable for anyone used to C/C++/TypeScript and even
           | Java.
           | 
           | The pure approach may be a bit off-putting at first, but the
           | apprehensions usually disappear after a first big refactoring
           | in the language. With the type system and Gleam's helpful
           | compiler, any significant changes are mostly a breeze, and
           | the code works as expected once it compiles.
           | 
           | There are escape hatches to TypeScript/JavaScript and Erlang
           | when necessary. But yeah, this does not really solve the
           | impure edges many people may cut themselves on.
        
           | dragonwriter wrote:
           | > Functional programming languages (almost always?) come with
           | the baggage of foreign looking syntax.
           | 
           | That increases the activation energy, I guess, for people who
           | have spent their whole programming life inside the algol-
           | derived syntax set of languages, but that's a box worth
           | getting out of independently of the value of functional
           | programming.
        
         | the__alchemist wrote:
         | I think it's because (I'm looking at Haskell in particular)
         | there are a lot of great ideas implemented in them, but the
         | purity makes writing practical or performant time-domain
         | programs high friction. But you don't need both purity and the
         | various tools they provide. You can use the tools without the
         | pure-functions model.
         | 
         | In particular: My brain, my computing hardware, and my problems
         | I solve with computers all feel like a better match for time-
         | domain-focused programming.
        
           | cogman10 wrote:
           | My problem with functional languages is there never seems to
           | be any easy way to start using them.
           | 
           | Haskell is a great example here. Last time I tried to learn
           | it, going on the IRC channel or looking up books it was
           | nothing but a flood of "Oh, don't do that, that's not a good
           | way to do things." It seemed like nothing was really settled
           | and everything was just a little broken.
           | 
           | I mean, Haskell has like what, 2, 3, 4? Major build systems
           | and package repositories? It's a quagmire.
           | 
           | Lisp is also a huge train wreck that way. One does not simply
           | "learn lisp" There's like 20+ different lisp like languages.
           | 
           | The one other thing I'd say is a problem that, especially for
           | typed functional languages, they simply have too many
           | capabilities and features which makes it hard to understand
           | the whole language or how to fit it together. That isn't
           | helped by the fact that some programmers love programming the
           | type system rather than the language itself. Like, cool, my
           | `SuperType` type alias can augment an integer or a record and
           | knows how to add the string `two` to `one` to produce `three`
           | but it's also an impossible to grok program crammed into 800
           | characters on one line.
        
             | v9v wrote:
             | > Lisp is also a huge train wreck that way. [...] There's
             | like 20+ different lisp like languages.
             | 
             | Lisp is not a language, but a descriptor for a family of
             | languages. Most Lisps are not functional in the modern
             | sense either.
             | 
             | Similarly, there are functional C-like languages, but not
             | all C-likes are functional, and "learn c-likes" is vague
             | the same way "learn lisp" is.
        
               | umanwizard wrote:
               | You're right and this is also a bit of a pet peeve of
               | mine. "Lisp" hasn't described a single language for more
               | than forty years, but people still talk about it as if it
               | were one.
               | 
               | Emacs lisp and Clojure are about as similar as Java and
               | Rust. The shared heritage is apparent but the experience
               | of actually using them is wildly different.
               | 
               | Btw, if someone wants to try a lisp that _is_ quite
               | functional in the modern sense (though not pure), Clojure
               | is a great choice.
        
             | valcron1000 wrote:
             | > I mean, Haskell has like what, 2, 3, 4? Major build
             | systems and package repositories? It's a quagmire.
             | 
             | Don't know when was the last time you've used Haskell, but
             | the ecosystem is mainly focused on Cabal as the build tool
             | and Hackage as the official package repository. If you've
             | used Rust:
             | 
             | - rustup -> ghcup - cargo -> cabal - crates.io -> hackage -
             | rustc -> ghc
        
               | cogman10 wrote:
               | It's admittedly been years.
               | 
               | ghcup didn't exist, AFAIK. Cabal was around but I think
               | there was a different ecosystem that was more popular at
               | the time (Started with an S, scaffold? Scratch? I can't
               | find it).
        
         | jandrewrogers wrote:
         | Pure functional works great until it doesn't. For a lot of
         | systems-y and performance-oriented code you need the escape
         | hatches or you'll be in for a lot of pain and annoyance.
         | 
         | As a practical observation, I think it was easier to close this
         | gap by adding substantial functional capabilities to imperative
         | languages than the other way around. Historically, functional
         | language communities were much more precious about the purity
         | of their functional-ness than imperative languages were about
         | their imperative-ness.
        
           | jancsika wrote:
           | Hehe, purity is one helluva drug!
           | 
           | For some reason, this makes me think of SVG's foreignObject
           | tag that gives a well-defined way to add elements into an SVG
           | document from an arbitrary XML namespace. Display a customer
           | invoice in there, or maybe a Wayland protocol. The sky's the
           | limit!
           | 
           | On the other hand, HTML had so many loose SVG tags scattered
           | around the web that browsers made a special case in the
           | parser to cover them without needing a namespace.
           | 
           | And we all know how that played out.
           | 
           |  _Posted from an xhtml foreignObject on my SVGphone_
        
           | Fire-Dragon-DoL wrote:
           | Unfortunately unless there's an explicit way to state what
           | has side effects like a mut keyword, a lot of fp programming
           | advantages lose value because most devs default to mutable
           | stuff, so the fp benefits don't compound
        
             | black_knight wrote:
             | I think this works quite well with IO in Haskell. Most of
             | my code is pure, but the parts which are really not, say
             | OpenGL code, is all marked as such with IO in their type
             | signatures.
             | 
             | Also, the STM monad is the most carefree way of dealing
             | with concurrency I have found.
        
           | christophilus wrote:
           | F# isn't purely functional, though, and strikes a nice
           | balance. I just don't really like the .NET ecosystem, being
           | 100% Linux these days, as it always seems slightly off to me
           | somehow.
        
             | pvdebbe wrote:
             | Same. I strikes me a great shame that there aren't great
             | F#-like languages that targeted the JVM.
        
               | bmitc wrote:
               | Really? What's wrong with .NET? It's one of the nicest
               | cross-platform platforms out there. What Microsoft has
               | done with it is amazing.
        
           | internet_points wrote:
           | > you need the escape hatches
           | 
           | Isn't this a strawman? Even Haskell has unsafePerformIO and
           | Debug.Trace etc. It just also provides enough ergonomics that
           | you don't need them so often, and they "light up" when you
           | see them, which is what we want: to know when we're mutating.
        
           | fsloth wrote:
           | "Pure functional works great until it doesn't. "
           | 
           | That's why F# is so great.
           | 
           | Functional is default, but mutable is quite easy to do with a
           | few mutable typedefs.
           | 
           | The containers are immutable, but nobody stops you from using
           | the vanilla mutable containers from .net
           | 
           | It's such a beautiful, pragmatic language.
        
           | bmitc wrote:
           | F# is not a pure functional language. It is a functional-
           | first language that does imperative and OOP better than
           | imperative and OOP languages.
           | 
           | You can program F# just like you would Python, and it will be
           | more readable, concise, performant, and correct.
        
         | keeda wrote:
         | We are still living with the hangover of C, which was designed
         | for the resource-starved machines of eons ago, and whose style
         | later languages felt they had to copy to get any kind of
         | adoption. (And as you point out, that is how things turned
         | out.)
         | 
         | My bet is functional programming will become more and more
         | prevalent as people figure out how to get AI-assisted coding to
         | work reliably. For the very reasons you stated, functional
         | principles make the code modular and easy to reason about,
         | which works very well for LLMs.
         | 
         | However, precisely because functional programming languages are
         | less popular and hence under-represented in the training data,
         | AI might not work well with them and they will probably
         | continue to remain fringe.
        
           | johnisgood wrote:
           | Just use OCaml in which you can mix imperative, functional,
           | and OOP. I use all of them in a single codebase, whichever,
           | wherever appropriate.
        
         | charcircuit wrote:
         | Const by default is not functional programming.
        
           | umanwizard wrote:
           | Indeed, but it's one of the (many) good ideas from functional
           | programming that have filtered into more mainstream
           | languages.
        
         | brrrrrm wrote:
         | one thing I've learned in my career is that escape hatches are
         | one of the most important things in tools made for building
         | other stuff.
         | 
         | dropping down into the familiar or the simple or the dumb is so
         | innately necessary in the building process. many things meant
         | to be "pure" tend to also be restrictive in that regard.
        
           | runevault wrote:
           | Functional languages are not necessarily pure though.
           | Actually outside Haskell don't most functional first
           | languages include escape hatches? F# is the one I have the
           | most experience with and it certainly does.
        
             | gf000 wrote:
             | For what it's worth, Haskell has plenty of escape hatches
             | itself as well.
        
         | johncolanduoni wrote:
         | I've done a significant amount of functional programming
         | (including F#) and still reach for it sometimes, but I don't
         | think it provides substantial advantages for most use-cases.
         | Local mutability is often clearer and more maintainable.
         | 
         | Also, category theorists think how excited people get about
         | using the word monad but then most don't learn any other
         | similar patterns (except maybe functors) is super cringe. And I
         | agree.
        
         | otikik wrote:
         | Don't get too comfortable, history likes oscillation. If it
         | becomes the norm then someone will have a "actually writing
         | imperatively makes things go super fast".
        
         | LogicHound wrote:
         | > It's funny how functional programming is slowly becoming the
         | best practice for modern code (pure functions, no side-
         | effects),
         | 
         | I once mentioned both these concepts to a room of C#
         | developers. Two of them were senior to me and it was a blank
         | expression from pretty much everyone.
         | 
         | > yet functional programming languages are still considered
         | fringe tech for some reason.
         | 
         | You can use the same concepts in non-functional programming
         | languages without having to buy into all the other gumpf around
         | functional programming languages. Also other programming
         | languages have imported functional concepts either into the
         | language itself or into the standard libraries.
         | 
         | Past that. It is very rare to be able to get a job using them.
         | The number of F# jobs I've seen advertised over the last
         | decade, I could count on one hand.
        
         | gf000 wrote:
         | It's because you want a tasteful mix of both.
         | 
         | I believe Scala was pretty ahead here by building the language
         | around local mutability with a general preference for immutable
         | APIs, and I think this same philosophy shows up pretty strongly
         | in Rust, aided by the borrow checker that sort of makes this
         | locality compiler-enforced (also, interior mutability)
        
           | dionian wrote:
           | Worth nothing that idiomatic scala uses constants by default,
           | variables are discouraged and frankly rare.
        
         | physicles wrote:
         | Agreed, and Carmack as always was ahead of the curve here.
         | 
         | In case anyone here hasn't seen it, here's his famous essay,
         | Functional Programming in C++:
         | 
         | http://sevangelatos.com/john-carmack-on/
         | 
         | He addresses your point:
         | 
         | > I do believe that there is real value in pursuing functional
         | programming, but it would be irresponsible to exhort everyone
         | to abandon their C++ compilers and start coding in Lisp,
         | Haskell, or, to be blunt, any other fringe language. To the
         | eternal chagrin of language designers, there are plenty of
         | externalities that can overwhelm the benefits of a language,
         | and game development has more than most fields.
        
         | skeezyjefferson wrote:
         | > If you want a language where const is the default and mutable
         | is a keyword
         | 
         | whats the difference between const and mutable?
        
           | Thorrez wrote:
           | "const" means something can't be changed. "mutable" means it
           | can be changed.
           | 
           | You don't need both a "const" keyword and a "mutable" keyword
           | in a programming language. You only need 1 of the keywords,
           | because the other can be the default. munchler is saying the
           | "const" keyword shouldn't exist, and instead all variables
           | should be constant by default, and we should have a "mutable"
           | keyword to mark variables as mutable.
           | 
           | As opposed to how C++ works today, where there is no
           | "mutable" keyword, variables are mutable by default, and we
           | use a "const" keyword to mark them constant.
        
             | alcover wrote:
             | > you don't need both a "const" keyword and a "mutable"
             | keyword
             | 
             | What if the lang has pointers? How express read-only?
        
               | skeezyjefferson wrote:
               | compiler flags with line and column number seems like the
               | easiest way
        
         | bmitc wrote:
         | I get your sentiment, but I side on it's infuriating that it's
         | taken this long. Lol.
         | 
         | F# is wonderful. It is the best general purpose language, in my
         | opinion. I looked for F# jobs for years and never landed one
         | and gave up. I write Rust now which has opened up the embedded
         | world, and it's nice that Rust paid attention to F# and OCaml.
        
         | danenania wrote:
         | It's because FP, great as it is, is most beneficial when
         | 80/20'd.
         | 
         | Trying to do it 100% pure makes everything take significantly
         | longer to build and also makes performance difficult when you
         | need it. It's also hard to hire/onboard people who can grok it.
        
         | themafia wrote:
         | I want a language that helps me write software. I do not need a
         | language that's hellbent on expressing a particular ideology.
        
       | hyperhello wrote:
       | > 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, list, set, listOf, array, vector, arrayOf,
       | Array.of(), etc unless it's going to get in my way because I have
       | ten CPU cores and I'll optimize the loop when I need to.
        
         | bee_rider wrote:
         | I don't have any useful ideas here but if you make a linter for
         | this sort of thing, I suggest calling it "mutalator."
        
         | estimator7292 wrote:
         | Your IDE probably supports this as an _explicit_ action.
         | JetBrains has a feature that can find all reads and writes to a
         | variable
        
           | Denvercoder9 wrote:
           | It also has the ability to style mutated variables
           | differently.
        
             | greenicon wrote:
             | Yes, depending on your highlighting scheme. Not every
             | highlighting scheme shows this by default, unfortunately.
             | 
             | To me, this seems initially like some very minor thing, but
             | I find this very helpful working with non-trivial code. For
             | larger methods you can directly discern whether a not-as-
             | immutable-declared variable behaves immutable nonetheless.
        
         | nielsbot wrote:
         | I use Swift for work. The compiler tell you this. If a mutable
         | variable is never mutated it suggests making it non-mutable.
         | And vice versa.
        
           | qmmmur wrote:
           | As will Typescript, at least using Biome to lint it does.
        
             | maleldil wrote:
             | eslint has this too:
             | https://eslint.org/docs/latest/rules/prefer-const
        
             | nielsbot wrote:
             | My very minor complaint about TypeScript is you use to use
             | `const` which is 2 additional letters.
             | 
             | Seriously though, I do find it slightly difficult to reason
             | about `const` vars in TypeScript because while a `const`
             | variable cannot be reassigned, the value it references can
             | still be mutated. I think TypeScript would benefit from
             | more non-mutable values types... (I know there are some)
             | 
             | Swift has the same problem, in theory, but it's very easy
             | to use a non-mutable value types in Swift (`struct`) so
             | it's mitigated a bit.
        
           | bartvk wrote:
           | Yup, it's pretty great. You get into the habit of
           | suspiciously eyeing every variable that's not a constant.
        
         | NathanaelRea wrote:
         | I don't think this is the best option, there could be very hard
         | bugs or performance cliffs. I think I'd rather have an explicit
         | opt-in, rather than the abstraction changing underneath me.
         | Have my IDE scream at me and make me consider if I really need
         | the opt-in, or if I should restructure.
         | 
         | Although I do agree with the sentiment of choosing a construct
         | and having it optimize if it can. Reminds me of a Rich Hickey
         | talk about sets being unordered and lists being ordered, where
         | if you want to specify a bag of non-duplicate unordered items
         | you should always use a set to convey the meaning.
         | 
         | It's interesting that small hash sets are slower than small
         | arrays, so it would be cool if the compiler could notice size
         | or access patterns and optimize in those scenarios.
        
           | bbminner wrote:
           | Right, sql optimizers are a good example - in theory it
           | should "just know" what is the optimal way of doing things,
           | but because these decisions are made at runtime based on
           | query analysis, small changes to logic might cause huge
           | changes in performance.
        
         | HDThoreaun wrote:
         | Clang-tidy's misc-const-correctness warns for this. Hook it up
         | to claude code and it'll const all non mutated mutables.
        
         | worthless-trash wrote:
         | If you write in erlang, emacs does this by default ;)
        
         | throwaway2037 wrote:
         | In my IntelliJ (a recent version), if I write a small Java
         | function like this:                   private static void
         | blah()         {             final int abc = 3;             for
         | (int def = 7; def < 20; ++def)             {
         | System.out.print(def);             }         }
         | 
         | The variable 'def' is underlined. Mouse-over hint shows:
         | 'Reassigned local variable'. To be clear, 'abc' is _not_
         | underlined. When I write Java, I try to use the smallest
         | variable scopes possible with as much final (keyword) as
         | possible. It helps me to write more maintainable code, that is
         | easier to read.
        
           | xxs wrote:
           | 1st) you use ++def in a loop, don't be weird; 2nd) if 'abc'
           | is to be used in the loop body, define in the loop, e.g. for
           | (int def = 7, abc =3; ...); 3rd) this is an IntelliJ bug -
           | both 'def' and 'abc' in the sample are always defined.
        
             | pacoverdi wrote:
             | 3) looks like you read 'underlined' as 'undefined'
        
               | xxs wrote:
               | true that, thanks!
        
             | sitzkrieg wrote:
             | the only thing that is weird is your lack of understanding
             | temporary variables
        
               | xxs wrote:
               | perhaps... yet, Java doesn't have a definition for
               | temporary variables
        
           | shagie wrote:
           | As an aside, you might also enjoy the inline inferred
           | annotations.
           | 
           | https://www.jetbrains.com/help/idea/annotating-source-
           | code.h...
           | 
           | Seeing @NotNull in there even if the author hasn't
           | specifically written that can help in understanding (and not
           | needing to consider) various branches.
        
           | e-topy wrote:
           | This works in RustRover as well! Super useful.
        
             | sn9 wrote:
             | Rust's type system specifically facilitates more powerful
             | tools: https://github.com/willcrichton/flowistry
        
         | considerdevs wrote:
         | Could Pylint help? It has atleast check for variable
         | redefinition:
         | https://pylint.pycqa.org/en/latest/user_guide/messages/refac...
        
       | gwbas1c wrote:
       | Years ago I did a project where we followed a lot of strict
       | immutability for thread safety reasons. (Immutable objects can be
       | read safely from multiple threads.)
       | 
       | It made the code easier to read because it was easier to track
       | down what could change and what couldn't. I'm now a huge fan of
       | the concept.
        
         | piker wrote:
         | You should check out Rust
        
           | gwbas1c wrote:
           | Rust wasn't available at the time.
           | 
           | It probably won't come as a surprise to you, but I am a big
           | fan of Rust.
        
       | ronnier wrote:
       | Immutability was gaining huge traction with Java... then large
       | parts of the industry switched to golang and we hardly make
       | anything immutable.
        
         | antonvs wrote:
         | Go is the new PHP.
        
           | LogicHound wrote:
           | Much like PHP, you can actually get stuff done unlike a lot
           | of other programming languages.
        
             | antonvs wrote:
             | Oh? Which "lot of" other programming languages can't you
             | "actually get stuff done" in? Are you sure the problem lies
             | with the programming language?
        
               | LogicHound wrote:
               | I find there are some environments where you have a
               | positive feedback loop while working in them. PHP is one
               | of them, Go is another at least for me.
               | 
               | I find many of languages I am constantly fighting with
               | dependency managers, upgrades and all sorts of other
               | things.
        
               | danenania wrote:
               | It's an exaggeration perhaps but I get the sentiment. FP
               | is elegant and beautiful and everything, but it can lead
               | you to spend all day puzzling out the right abstractions
               | for some data transformation that takes 5 minutes with a
               | dumb for loop in Go.
        
         | ottah wrote:
         | I would kill for an immutable error type
        
         | XorNot wrote:
         | Go desperately needs support for immutable structs that copy
         | cleanly.
         | 
         | I want to be able to write `x := y` and be sure I don't have
         | mutable slices and pointer types being copied unsafely.
        
       | bitwize wrote:
       | Hey Carmack, I heard a few hackers are working on a highly
       | experimental language called Rust where everything is immutable
       | by default and you have to declare mutable things 'mut'.
        
         | embedding-shape wrote:
         | Eh, Rust is kind of "immutable by default" but not really
         | enforcing that + "constants" in the way I think Carmack
         | advocates for. Other languages does better in that regard.
         | Examples: https://play.rust-
         | lang.org/?version=stable&mode=debug&editio...
        
           | jasonthorsness wrote:
           | Yeah the encouragement of shadowing is a little weird
           | (learning Rust coming from Go where it is sort of
           | discouraged)
        
             | tialaramex wrote:
             | Discouraging shadowing in languages with unclear lifetimes
             | makes plenty of sense.
             | 
             | Clippy offers lints for (three?) distinct ways of shadowing
             | because in most cases it turns out people who don't like
             | shadowing only had problems with typically one specific
             | kind of shadowing (e.g. same type unrelated shadowing, or
             | different type same value shadowing) and since that varies
             | why not offer to diagnose the specific problem this
             | programmer doesn't like.
             | 
             | To be concrete some people are worried about things like:
             | let sparrows = get_a_list_of_sparrows();       // ....
             | let sparrows = sparrows.len() + EXTRA_SPARROWS;
             | 
             | Whereas for some people that seems fine but they're worried
             | about:                  let sparrows =
             | find_wild_sparrows();        // ....        let sparrows =
             | find_farmed_sparrows();
             | 
             | These are both shadowing the name sparrows, and Rust is
             | fine with either but Clippy can provide different lints so
             | that you can ban one of them while using the other freely
             | in your codebase.
        
         | veltas wrote:
         | I hope this experimental language becomes usable eventually,
         | all I hear is how you have to continuously rewrite to work
         | around the borrow checker, accept worse performance to make
         | everything message based, or just ignore the type system and
         | write VB6-style code where everything is indexes in arrays to
         | obfuscate ownership and paste 'unsafe' when it moans.
        
       | lopatin wrote:
       | How fast this got to the top, you would think John Carmack just
       | invented nuclear fusion.
        
         | viraptor wrote:
         | Sometimes it's nice to be reminded of some basic good ideas.
         | Even if you already know. Also https://xkcd.com/1053/
        
         | AndrewOMartin wrote:
         | I have no doubt that he could, not only invent nuclear fusion,
         | but get it running on a Pentium 90.
        
         | villgax wrote:
         | Just like AGI he was supposedly brought on board for
         | but.....checks notes. Nothing.
        
           | jpgvm wrote:
           | Dude isn't a god.
           | 
           | That said it's worth listening when he chimes in about C/C++
           | or optimisation as he has earned his respect when it comes to
           | these fields.
           | 
           | Will he crack AGI? Probably not. He didn't crack rockets
           | either. Doesn't make him any less awesome, just makes him
           | human.
        
             | TheAceOfHearts wrote:
             | There are certain figures who are very experienced and
             | knowledgeable in certain domains, so when they speak up
             | about a topic it's usually worth listening to them. That
             | doesn't mean they're always going to be correct, and they
             | shouldn't be worshiped as superhuman entities, but it's
             | almost always a bad idea to completely ignore them.
        
           | AnotherGoodName wrote:
           | >Just like AGI he was supposedly brought on board for
           | but.....checks notes. Nothing.
           | 
           | His AGI work was entirely his own? As in he literally stepped
           | down from a high level corporate role where he was
           | responsible for Oculus (3D games/applications) to do this in
           | his own time. Similar to his work on Armadillo Aerospace.
        
         | trallnag wrote:
         | How many hours of discussions went into topics like code
         | formatting and naming things like variables, endpoints,
         | classes, etc?
        
         | devnullbrain wrote:
         | I was part of the Carmack cult but the illusion was broken when
         | I saw him use the same authoratative tone on a subject I'm more
         | knowledgable about.
        
           | metaltyphoon wrote:
           | Such as? Any links to this?
        
           | maleldil wrote:
           | Good old Gell-Mann Amnesia! It doesn't mean he's incompetent
           | in his core area of expertise, though.
        
           | carabiner wrote:
           | I don't think gellman amnesia is really a material issue.
           | Carmack is indubitably an expert in his field, but that
           | doesn't mean he's an expert in every field (like aerospace or
           | AI). I'm an expert in some things, but I've probably said
           | some stupid shit in other fields where I dabble such as
           | cooking, playing music, raising cats.
        
         | bmitc wrote:
         | People worship this guy, but other than being a good C++
         | graphics programmer, it isn't clear what he's actually done.
        
           | modeless wrote:
           | His engines are open source, and graphics are far from the
           | only interesting thing about them. If you don't know what
           | he's done that's on you; it's no secret.
        
             | bmitc wrote:
             | So again, what has he done successfully besides C++
             | graphics?
        
               | zenlot wrote:
               | More than you will ever do mate.
        
               | bmitc wrote:
               | That's not part of my argument.
        
           | rvba wrote:
           | Doom which was countless fun for people up to this day who
           | make mods? (E.g. the great Myhouse.wad that was perhaps FPS
           | of the year... 2023)
           | 
           | Quake, which was a good game, but arguably a better engine
           | that lead to things like Half-Life 1?
           | 
           | Other games?
           | 
           | Shared the code to Doom and Quake?
           | 
           | I guess you dont understand how big of a game Doom was. The
           | first episode holds suprisngly well up to this day, even
           | after hundreds of _doom-clones_ as they used to call FPS
           | games.
        
             | bmitc wrote:
             | But he didn't design the Doom game. He designed its
             | graphics engine.
        
               | zenlot wrote:
               | You understand how games work, do you? Please tell me you
               | do. Else, what kind of argument is it?
        
               | tredre3 wrote:
               | The engine enables people with actual creativity to
               | realize their vision. I believe Carmack was part of that
               | creative process, but the condescending tone of your
               | comment really isn't appropriate because in most games
               | the engine is just a means to an end (nobody sane
               | idolizes games because they're using unreal underneath,
               | for example)..
        
               | bmitc wrote:
               | What is your point?
        
           | tom_ wrote:
           | Well some people do, I'm sure, but most people just pay
           | ordinary levels of attention to him. And they do that because
           | he's made interesting contributions to multiple products that
           | people like using - which is enough, surely?
           | 
           | (Regarding this specific tweet, this seems to be him visiting
           | his occasional theme of how to write C++ in a way that will
           | help rather than hinder the creation of finishable software
           | products. He's qualified to comment.)
        
         | throwaway314155 wrote:
         | Right? This isn't even a hot take - it's just standard software
         | engineering advice we all learn in school or on the job.
        
       | noduerme wrote:
       | Why loops specifically? Why not conditionals?
       | 
       | A lot of code needs to assemble a result set based on if/then or
       | switch statements. Maybe you could add those in each step of a
       | chain of inline functions, but what if you need to skip some of
       | that logic in certain cases? It's often much more readable to
       | start off with a null result and put your (relatively functional)
       | code inside if/then blocks to clearly show different logic for
       | different cases.
        
         | turtletontine wrote:
         | There's no mutating happening here, for example:
         | if cond:           X = "yes"       else:           X = "no"
         | 
         | X is only ever assigned once, it's actually still purely
         | functional. And in Rust or Lisp or other expression languages,
         | you can do stuff like this:                 let X = if cond {
         | "yes" } else { "no" };
         | 
         | That's a lot nicer than a trinary operator!
        
           | nielsbot wrote:
           | Swift does let you declare an immutable variable without
           | assigning a value to it immediately. As long as you assign a
           | value to that variable once and only once on every code path
           | before the variable is read:                   let x: Int
           | if cond {             x = 1         } else {              x =
           | 2         }              // read x here
        
             | ruszki wrote:
             | Same with Java and final variables, which should be the
             | default as Carmack said. It's even a compile time error if
             | you miss an assignment on a path.
        
             | sambishop wrote:
             | that's oldschool swift. the new hotness would be
             | let x = if cond { 1 } else { 2 }
        
           | lock1 wrote:
           | IMO, both ternary operator form & Rust/Haskell/Zig syntax
           | works pretty well. Both if expression syntax can be easily
           | composed and read left-to-right, unlike Python's `<true-
           | branch> if <cond> else <false-branch>`.
        
       | shmerl wrote:
       | Proposing making immutable by default in C or C++ doesn't make
       | sense due to backwards compatibility reasons. New languages like
       | Rust have easier time making better choices with immutable by
       | default.
        
         | loeg wrote:
         | cpp2/cfront could plausibly do this, right? Except he doesn't
         | want to:
         | 
         | https://github.com/hsutter/cppfront/wiki/Design-note%3A-cons...
        
           | shmerl wrote:
           | Well, if they are willing to break backwards compatibility, a
           | lot of things can be improved, including this.
        
         | NathanaelRea wrote:
         | They could just add a "use immutable;" directive that you place
         | at the top of your file.
        
           | Pxtl wrote:
           | C# does this with the null hole. I wish more languages would
           | take a versioning approach to defaults at the file-level.
        
         | JonChesterfield wrote:
         | Could be a compiler flag. -const-by-default. Would probably
         | mean you need to scatter mutable across the codebase to get it
         | to compile, but I expect some people would like to have every
         | local annotated as const or mutable.
        
         | _flux wrote:
         | Maybe the new C++ profiles that are supposedly going to make
         | C++ a safe language could do it.
        
       | GPerson wrote:
       | Kinda curious on what jblow would say about this.
        
         | kragen wrote:
         | blojo? Ask him and report back.
        
       | loeg wrote:
       | Yeah, I also wish it was the default. But it's a little too
       | verbose to just sprinkle on every variable in C++. Alas. Rust
       | gets this right, but I'm stuck with C++ at work.
        
         | veltas wrote:
         | 100%, life's too short.
         | 
         | Ultra-pedantic const-correctness (vs tasteful const-correctness
         | on e.g. pass-by-reference arguments or static/big objects)
         | catches nearly no bugs in practice and significantly increases
         | the visual noise of your code.
         | 
         | If you have luxury of designing a new language or using one
         | with default mutability then do so, but don't turn C coding
         | styles into C++-envy, or C++ coding styles into Rust-envy.
        
         | maleldil wrote:
         | > But it's a little too verbose to just sprinkle on every
         | variable in C++
         | 
         | It's worth it, though. Every variable that isn't mutated should
         | be const. Every parameter not mutated should be const, and so
         | should every method that doesn't mutate any fields. The mutable
         | keyword should be banned.
        
           | loeg wrote:
           | Const annotations are worth it for methods and APIs, but not
           | most local variables.
        
       | DashAnimal wrote:
       | https://nitter.net/id_aa_carmack/status/1983593511703474196
        
       | stevage wrote:
       | In JavaScript, I really like const and have adopted this
       | approach. There are some annoying situations where it doesn't
       | work though, to do with scoping. Particularly:
       | 
       | - if (x) { const y = true } else { const y = false } // y doesn't
       | exist after the block - try { const x = foo } catch (e) { } // x
       | doesn't exist after the try block
        
         | NathanaelRea wrote:
         | You could do an absolutely disgusting IIFE if you need the
         | curly brace spice in your life, instead of a typical JS
         | ternary.                 const y = (() => {         if (x) {
         | return true;         } else {           return false;
         | })();
        
           | cookiengineer wrote:
           | Technically you could just use an assignment ternary
           | expression for this:                   const y = (x === true)
           | ? true : false;
           | 
           | I used this kind of style for argument initialization when I
           | was writing JS code, right at the top of my function bodies,
           | due to ES not being able to specify real nullable default
           | values. (and I'm setting apart why I think undefined as a
           | value is pointless legacy).
           | Composite.prototype.SetPosition(x, y, z) {                  x
           | = (isNumber(x) && x >= 0 && x <= 1337) ? x : null;
           | y = (isNumber(y) && y >= 0 && y <= 1337) ? y : null;
           | z = isNumber(z) ? z : null;                  if x !== null &&
           | y !== null && z !== null {                 // use clamped
           | values             }              }
        
             | NathanaelRea wrote:
             | I typically only use ternaries for single operations and
             | extract to a function if it's too big. Although they are
             | quite fun in JSX. For your code i'd probably do:
             | function SetPosition(x, y, z) {         if (!(isNumber(x)
             | && isNumber(y) && isNumber(z))) {           // Default vals
             | return;         }         x = clamp(x, 0, 1337);         y
             | = clamp(y, 0, 1337);         z = z;       }
        
               | cookiengineer wrote:
               | I always call this the difference of return branch
               | styles. Yours I'd describe as "fast fail" aka return
               | false as quickly as possible (for lack of a better
               | terminology) whereas I personally prefer to have a single
               | return false case at the bottom of my function body, and
               | the other validation errors (e.g. in Go) are usually in
               | the else blocks.
               | 
               | In JS, errors are pretty painful due to try/catch, that's
               | why I would probably these days recommend to use Effect
               | [1] or similar libraries to have a failsafe workflow with
               | error cases.
               | 
               | Errors in general are pretty painful in all languages in
               | my opinion. The only language where I thought "oh this
               | might be nice" was Koka, where it's designed around
               | Effect Types and Handlers [2]
               | 
               | [1] https://effect.website/
               | 
               | [2] https://koka-lang.github.io/koka/doc/index.html
        
             | fuzzythinker wrote:
             | nitpick: cleaner w/o ()'s, as '=' is the 2nd lowest
             | operator, after the comma separation operator.
        
           | stevage wrote:
           | I love it.
        
         | askmrsinh wrote:
         | Why not do:
         | 
         | const y = x ? true : false;
        
           | stevage wrote:
           | I'm talking about cases with additional logic that's too long
           | for a ternary.
        
           | 1718627440 wrote:
           | That sounds like a more complicated way to write
           | const y = (bool)x;
           | 
           | or                   const bool y = x;
        
         | keeda wrote:
         | Ditto. These days those are the only cases where I use "let" in
         | JS. The thing I miss most from Kotlin is the ability to return
         | values from blocks, e.g.
         | 
         | val result = if (condition) { val x = foo() y = bar(x) y + k //
         | return of last expression is return value of block } else {
         | baz() }
         | 
         | Or:
         | 
         | val q = try { a / b } catch (e: ArithmeticException) {
         | println("Division by zero!") 0 // Returns 0 if an exception
         | occurs }
         | 
         | Edit: ugh, can't get the formatting to work /facepalm.
        
         | latexr wrote:
         | JavaScript's `const` has the bigger issue that while things
         | can't be reassigned, they can still mutate. For example:
         | const myArray = [1,2,3]       myArray.push(4)       myArray //
         | [1, 2, 3, 4]
        
           | stevage wrote:
           | In what way is that an issue?
        
             | maleldil wrote:
             | Because this isn't immutability. The goal is to have a way
             | to define an object that will never change after
             | initialisation, and JS's const isn't it.
        
               | stevage wrote:
               | Clearly that isn't the goal.
        
       | askmrsinh wrote:
       | Constants are useful for reasoning about code, but anyone who
       | focuses only on making everything an immutable is missing the
       | point. The main goal should be achieving referential
       | transparancy.
       | 
       | It can be perfectly fine to use mutable variables within a block,
       | like a function when absolutely needed - for example, in
       | JavaScript's try catch and switch statements that need to set a
       | variable for later use. As long as these assignments are local to
       | that block, the larger code remains side-effect free and still
       | easy to reason about, refactor and mantain.
       | 
       | https://rockthejvm.com/articles/what-is-referential-transpar...
        
       | swiftcoder wrote:
       | Rediscovering one of the many great decisions that Erlang made
        
         | DarkNova6 wrote:
         | To be fair, Carmack has advocated for immutability and other
         | good practices at least since the 2000s.
        
           | signa11 wrote:
           | erlang predates that.
        
             | eps wrote:
             | I doubt the GP tried to imply the opposite.
        
               | signa11 wrote:
               | maybe, i don't really know.
               | 
               | his comment was in response to the
               | 
               | ```                   Rediscovering one of the many great
               | decisions that Erlang made
               | 
               | ```
               | 
               | and seemed to me to insinuate just that.
        
           | rurban wrote:
           | Maybe he read SICP then. But he still didn't write a compiler
           | since
        
         | gorgoiler wrote:
         | I still have a habit of naming variables foo0, foo1, foo2, from
         | a time working in Erlang many years ago.
        
       | slifin wrote:
       | Yeah I wish variables were immutable by default and everything
       | was an expression
       | 
       | Oh well _continues day job as a Clojure programmer that is
       | actively threatened by an obnoxious python take over_
        
         | sunrunner wrote:
         | As a Python programmer at day job, that is Clojure-curious and
         | sadly only gets to use it for personal projects, and is
         | currently threatened by an obnoxious TypeScript take over, I
         | feel this.
        
           | dude250711 wrote:
           | Removing barriers to sloppy code is a language feature.
           | 
           | That is why vibe coding, JavaScript and Python are so
           | attractive.
        
             | Xelbair wrote:
             | Removing barriers to civil engineering building design is a
             | feature.
             | 
             | Who needs to calculate load bearing supports, walls, and
             | floors when you can just vibe oversize it by 50%.
        
               | huflungdung wrote:
               | Well if it does the job. So what?
        
           | hn_throw2025 wrote:
           | In the context of the original discussion, TypeScript (and
           | ES6) has const and let.
        
             | sunrunner wrote:
             | Fair enough about const and let, the obnoxiousness for me
             | is a combination of the language ergonomics, language
             | ecosystem, but mostly the techno-political decision making
             | behind it.
        
             | dragonwriter wrote:
             | Neither let nor even const are immutable (const prevents
             | reassignment but not mutation if the value is of a mutable
             | type like object or array).
        
             | AstroBen wrote:
             | well yeah except const doesn't make objects or arrays
             | immutable
        
               | catlifeonmars wrote:
               | I feel that Java's "final" would have been a better
               | choice than "const". It doesn't have the same confusing
               | connotation.
        
               | MetaWhirledPeas wrote:
               | Yeah it makes their structure immutable? Something like
               | that. Not useless but not what you would expect.
               | 
               | But for non-objects and non-arrays it's fine.
        
           | Garlef wrote:
           | If you avoid metaprogramming and stick to the simple stuff,
           | python and typescript are almost the same language.
           | 
           | To be fair, comprehensions (list/object expresions) are a
           | nice feature that I miss a lot in JS/TS. But that's about it.
        
         | sgt wrote:
         | Clojure will always be faster than Python. So you have that, at
         | least.
        
         | ziml77 wrote:
         | Rust taught me that a language does not have to be purely
         | functional to have everything be an expression, and ever since
         | I first used Rust years ago I've been wishing every other
         | language worked that way. It's such a nice way to avoid or
         | limit the scope of mutations
        
         | nvader wrote:
         | You are not a Clojure programmer. You use Clojure to solve
         | problems in a professional context. I'm sorry that there's a
         | political tribal war based on language going on at your
         | workplace.
         | 
         | But especially now that coding agents are radically enabling
         | gains in developer productivity, you don't need to feel
         | excluded by the artificial tribal boundaries.
         | 
         | If you haven't, I recommend reading:
         | https://www.kalzumeus.com/2011/10/28/dont-call-yourself-a-pr...
        
           | jimbokun wrote:
           | You know I read this when it came out but have gotten out of
           | the habit of applying it.
           | 
           | Thanks for the reminder. Will work on putting these ideas
           | back into practice again.
        
           | sunrunner wrote:
           | I remember that post and essentially agree with everything in
           | it and your points too.
           | 
           | However there's a real-world factor that I don't think it
           | covers, which is that having ten years of experience in the
           | ecosystem for _any_ language almost guarantees that you 're
           | going to be faster, more efficient, more idiomatic, and
           | generally more comfortable through familiarity with that
           | language and its ecosystem than with any other 'drop in
           | replacement'. And you'll also probably be more aware of what
           | _doesn 't_ work, which is just as useful. You can always tell
           | when someone knows their tools well when they can immediately
           | tell you what _sucks_ about them, and possibly even the
           | history of it and why it might happen to make sense, even if
           | seems bad.
           | 
           | This isn't an argument for favouring speed or efficiency,
           | just an ackowledgement of what is lost when you choose or are
           | forced to move to a different environment.
           | 
           | Languages are a lot more than just syntax. Language-specific
           | features, conventions and common idioms, language
           | implementation details that end up being valuable to
           | understand, familiarity with core library, familiarity with
           | third party libaries (including the ones that are so well-
           | known as to almost be considered core), package management,
           | documentation standards, related tooling, foreign-function
           | interfaces and related tools to make _that_ workable, release
           | concerns. The list goes on.
           | 
           | There's no tribal boundary here, just a belief that time
           | spent with a given tool and all its idiosyncrancies (and
           | programming languages _are_ their idosyncracies, otherwise
           | they wouldn 't be different) is valuable and not something to
           | pass up, even if I agree with the thesis of the article.
           | 
           | Can you bootstrap your way to a passable, possibly even
           | idiomatic, solution with coding agents? Yes. Does that mean
           | you've managed to short circuit the results of long-term
           | experience? I'm not so sure. Does it matter? Depends on the
           | person or environment, I guess.
           | 
           | I don't think the learning curve for a new tool is a straight
           | line (I imagine more logarithmic), so it's not that you'd
           | need the same amount of exposure in terms of time, but that
           | does imply the cost of changing is up-front.
           | 
           | There's also a difference between choosing to investigate a
           | new language out of your own interest and having the time to
           | do it properly, versus having some top-down mandate that you
           | must now use <X>, meanwhile still having to meet the same
           | deadlines as before.
        
       | mr_mitm wrote:
       | Is there a ruff rule for this?
        
         | maleldil wrote:
         | There are [1] and [2] for function arguments and loop
         | variables, respectively, but nothing for the general case. Note
         | that a type checker will complain if you re-assign with a
         | different type. Pylint also has [3] for redefining variables
         | from an outer scope, but Ruff doesn't implement that yet.
         | 
         | [1] https://docs.astral.sh/ruff/rules/redefined-argument-from-
         | lo...
         | 
         | [2] https://docs.astral.sh/ruff/rules/redefined-loop-name/
         | 
         | [3]
         | https://pylint.pycqa.org/en/latest/user_guide/messages/warni...
        
       | gethly wrote:
       | Variable is by definition mutable.
       | 
       | Constant is by definition immutable.
       | 
       | Why can't people get it through their heads in 2025? (I'm looking
       | at you, Rust)
        
         | mirpa wrote:
         | That depends on your definition. Programming languages often
         | deviate from mathematics when it comes to definition of
         | variables, functions etc. That is by choice, Haskell tried to
         | be as close to mathematical definition as possible.
        
           | latexr wrote:
           | You don't need to make a mathematical argument, "variable"
           | and "constant" have clear meanings in colloquial use which
           | match the definitions of your parent comment.
        
             | devnullbrain wrote:
             | So does 'parent'.
        
         | m000 wrote:
         | Depends on how you look at it.
         | 
         | A true constant won't change between runs of the code. I.e. it
         | is essentially a symbolic name for a literal.
         | 
         | A constant variable OTOH, varies in different executions of the
         | code. So, its invariance is linked to an execution context.
        
         | gf000 wrote:
         | What about the result of a computation within a giant algorithm
         | that I decided to name, and don't plan on rewriting it anymore?
         | 
         | That's surely not a constant like PI, is it?
        
         | witx wrote:
         | A constant is a variable that _always_ has the same value on
         | all lifecycles, e.g.                   const int a{10};
         | 
         | An immutable variable well ... does not                  void
         | some_function(const int a);
         | 
         | Can you tell me what is the value of `a` on both cases?
        
           | gethly wrote:
           | > A constant is a variable...
           | 
           | No, it is not. A constant is the direct opposite of a
           | variable.
           | 
           | > An immutable variable..
           | 
           | There is no such thing. You can decide not to mutate it but
           | variable is by definition mutable.
           | 
           | If you want to argue mutability, then you have to talk about
           | the data structure or memory footprint of the constant or
           | variable that it points to or represents, not the concept of
           | variable or constant itself.
           | 
           | In other words, we can have var foo = 5 and const bar = 5.
           | foo can be changed by being reassigned another value with
           | simple foo = 6, whereas bar cannot as bar = 6 should cause
           | panic/exception/... On the other hand, we can have var foo =
           | {value: 5} and const bar = {value: 5} and now it depends on
           | the language how it handles complex types like a
           | struct/object as the operation now bypasses the guards on the
           | variable/constant assignment itself. Will it guard against
           | mutation or not? It should, but that is rarely the case.
           | Hence, in most languages, we will be able to do foo.value = 6
           | and also bar.value = 6, even though we should not. But again,
           | now we are arguing about the mutability of the data type or
           | memory representation and not the variable/constant itself.
           | Most languages don't care about mutability, so we have this
           | flawed thinking where we are simply unable to strictly define
           | what data is actually mutable and what data is not. Rust uses
           | the borrow checker, that is one approach, but generally this
           | should be properly handled by the language spec and compiler
           | itself and we should not even have this conversation where
           | programmers simply cannot make a distinction between
           | variables and constants, let alone comprehend what those
           | terms mean in the first place, as those meanings have been
           | thrown out of the window by the folks designing the
           | languages.
        
           | pasteldream wrote:
           | I'm not sure why witx's comment was downvoted. This is
           | absolutely the standard usage in math and computer science.
        
       | halo wrote:
       | On a similar note, I've always liked the idea of being able to
       | mark functions as pure (for some reasonable definition of pure).
       | 
       | The principle of reducing state changes and side-effects feels a
       | good one.
        
         | jlmcgraw wrote:
         | For what it's worth, I was experimenting with this idea for
         | Python (in an almost completely vibe-coded fashion) here:
         | https://github.com/jlmcgraw/pure-function-decorators
         | 
         | Whether it's of any actual utility is debatable
        
       | omnicognate wrote:
       | > In C/C++, making almost every variable const at initialization
       | is good practice. I wish it was the default, and mutable was a
       | keyword.
       | 
       | I know it's irrelevant to his point, and it's not true of C, and
       | it doesn't have the meaning he wants, but the pedant in me is
       | screaming and I'm surprised it hasn't been said in the comments:
       | 
       | In C++ mutable _is_ a keyword.
        
         | tsimionescu wrote:
         | It's also really funny, since C++ is like a malicious genie
         | with this. The author wished for _mutable_ to be a keyword, and
         | C++ made the wish come true: it 's a keyword that _removes_
         | some of the guarantees of _const_!
        
       | y0ned4 wrote:
       | When I started programming in Haskell, where all variables are
       | immutable, I felt like I was in a straitjacket
       | 
       | Then, suddenly, the enlightenment
        
         | kypro wrote:
         | I've had this experience with going from PHP and JS to typed
         | languages. Many years ago I was a type sceptic, but after being
         | forced to use them, I now can't stand not having strict typing.
         | 
         | I'm sure others have written about this, but these days I think
         | good code is code which has a very small area of variability.
         | E.g code which returns a value in a single place as a single
         | type, has a very limited number of params (also of single
         | type), and has no mutable variables. If you can break code into
         | chunks of highly predictable logic like this it's so so much
         | easier to reason about your code and prevent bugs.
         | 
         | Whenever I see methods with 5+ params and several return
         | statements I can almost guarantee there will be subtle bugs.
        
           | kalaksi wrote:
           | I initially started programming with C++, then did a bunch of
           | scripting languages and then Rust and C#. I feel like there
           | are pros and cons to strictness.
           | 
           | I don't like all scripting languages, but Python, for
           | example, has a simple syntax and is easy and fast to learn
           | and write. I think it also has a good standard library. Some
           | things can be simpler because of the missing guardrails. But
           | that's also the weakness and I wouldn't use it for large and
           | complex software. You have to be more mindful to keep a good
           | style because of the freedoms.
           | 
           | C++ and Rust are at the opposite end. Verbose to write and
           | more difficult syntax. More stuff to learn. But in the end,
           | that's the cost for better guarantees on correctness. But
           | again, they don't fit all use cases as well as scripting
           | languages.
           | 
           | I've experienced the good and the bad in both kind of
           | languages. There are tradeoffs (what a surprise) and it's
           | probably also subjective what kind of language one prefers. I
           | think my _current_ favorites are Rust, C# and Python.
        
           | maleldil wrote:
           | > Whenever I see methods with 5+ params
           | 
           | I don't see why that's a problem. If a function implements an
           | algorithm with several parameters (e.g. a formula with
           | multiple variables), those values have to be passed somehow.
           | Does it make a difference if they're in a configuration
           | object or as distinct parameters?
        
           | runevault wrote:
           | Personally I'd tweak your last sentence to "return statements
           | in the middle of a function."
           | 
           | Early returns at the very top for things like None if you
           | pass in an Option type don't increase the risk of bugs, but
           | if you have a return nested somewhere in the middle it makes
           | it easier to either write bugs up front or especially have
           | bugs created during a refactor. I certainly have had cases
           | where returns in the middle of a beefy function caused me
           | headaches when trying to add functionality.
        
         | blub wrote:
         | How do you know it's real? When one is in a straitjacket for a
         | long time, the trauma might make the mind disassociate from the
         | body, giving an illusion of freedom.
         | 
         | The brain is essentially dreaming it's escaped while the body
         | is still programming in Java.
        
           | archerx wrote:
           | That made me laugh, thank you.
        
         | criddell wrote:
         | How does Haskell deal with things like memory-mapped I/O or
         | signal handlers where the value of a variable can be changed by
         | factors outside of the programmer's control?
        
           | how_gauche wrote:
           | Side effecting computations that depend on the "real world"
           | go into an IO monad. The game in Haskell is shifting as much
           | of the codebase as possible into pure functions/non-side-
           | effecting code, because it's easier to reason about and prove
           | correct.
        
           | chuckadams wrote:
           | IORefs usually, which can only be manipulated within the IO
           | monad, so they tend to only get used at the top level and
           | passed down to pure functions as parameters.
        
         | chuckadams wrote:
         | The lovely thing with Haskell is you have the ST monad that
         | lets you have as many mutable variables as you want, as long as
         | they stay within ST.
        
       | jdthedisciple wrote:
       | This is almost standard in modern languages:
       | 
       | For example in Dart you make everything `final` by default.
        
         | croes wrote:
         | A real default doesn't need a keyword.
        
       | EastLondonCoder wrote:
       | 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.
        
         | m_rpn wrote:
         | salutes from a WestLondonCoder
        
         | emil0r wrote:
         | 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. At no
         | point do you ever need to know about the state of the entire
         | program, you just need to know the data and the function. I
         | don't need the entire program up and running in order to test
         | or debug this function. I just need the data that was sent in,
         | which CANNOT be changed by any other part of the program.
        
           | DrScientist wrote:
           | 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?
        
             | maleldil wrote:
             | > there still has to be mutable state somewhere - so where
             | is it moved to?
             | 
             | This is one way of thinking about it:
             | https://news.ycombinator.com/item?id=45701901 (Simplify
             | your code: Functional core, imperative shell)
        
             | raddan wrote:
             | In functional programs, you very explicitly _do not_ need
             | to understand an entire program. You just need to know that
             | a function does a thing. When you're implementing a
             | function-- sure, you need to know what it does. But you're
             | defining it in such a way that the user should not know
             | _how_ it works, only _what_ it does. This is a major
             | distinction between programs written with mutable state and
             | those written without. The latter is _much_ easier to think
             | about.
             | 
             | I often hear from programmers that "oh, functional
             | programming must be hard." It's actually the opposite.
             | Imperative programming is hard. I choose to be a functional
             | programmer because I am dumb, and the language gives me
             | superpowers.
        
               | DrScientist wrote:
               | I think you missed the point. I understand that if you
               | writing a simple function with an expected
               | interface/behaviour then that's all you need to
               | understand. Note this isn't something unique to a
               | functional approach.
               | 
               | However, _somebody_ needs to know how the entire program
               | works - so my question was where does that application
               | state live in a purely functional world of immumutables?
               | 
               | Does it disappear into the call stack?
        
               | MetaWhirledPeas wrote:
               | It didn't disappear; there's just less of it. Only the
               | stateful things need to remain stateful. Everything else
               | becomes single-use.
               | 
               | Declaring something as a constant gives you license to
               | only need to understand it _once_. You don 't have to
               | trace through the rest of the code finding out new ways
               | it was reassigned. This frees up your mind to move on to
               | the next thing.
        
               | raddan wrote:
               | > Only the stateful things need to remain stateful.
               | 
               | And I think it is worth noting that there is effectively
               | no difference between "stateful" and "not stateful" in a
               | purely functional programming environment. You are mostly
               | talking about what a thing is and how you would like to
               | transform it. Eg, this variable stores a set of A and I
               | would like to compute a set of B and then C is their set
               | difference. And so on.
               | 
               | Unless you have hybrid applications with mutable state
               | (which is admittedly not uncommon, especially when using
               | high performance libraries) you really don't have to
               | think about state, even at a global application level. A
               | functional program is simply a sequence of
               | transformations of data, often a recursive sequence of
               | transformations. But even when working with mutable
               | state, you can find ways to abstract away some of the
               | mutable statefulness. Eg, a good, high performance
               | dynamic programming solution or graph algorithm often
               | needs to be stateful; but at some point you can "package
               | it up" as a function and then the caller does not need to
               | think about that part at all.
        
             | jimbokun wrote:
             | > However, don't you still need to understand the entire
             | program as ultimately that's what you are trying to build.
             | 
             | Of course not, that's impossible. Modern programs are way
             | to large to keep in your head and reason about.
             | 
             | So you need to be able to isolate certain parts of the
             | program and just reason about those pieces while you debug
             | or modify the code.
             | 
             | Once you identify the part of the program that needs to
             | change, you don't have to worry about all the other parts
             | of the program while you're making that change as long as
             | you keep the contracts of all the functions in place.
        
               | DrScientist wrote:
               | > Once you identify the part of the program that needs to
               | change,
               | 
               | And how do you do that without understanding how the
               | program works at a high level?
               | 
               | I understand the value of clean interfaces and
               | encapsulation - that's not unique to functional
               | approaches - I'm just wondering in the world of pure
               | immutability where the application state goes.
               | 
               | What happens if the change you need to make is at a level
               | higher than a single function?
        
               | jimbokun wrote:
               | Yes, obviously a program with no mutability only heats up
               | the CPU.
               | 
               | The point is to determine the points in your program
               | where mutation happens, and the rest is immutable data
               | and pure functions.
               | 
               | In the case of interacting services, for example,
               | mutation should happen in some kind of persistent store
               | like a database. Think of POST and PUT vs GET calls. Then
               | a higher level service can orchestrate the component
               | services.
               | 
               | Other times you can go a long way with piping the output
               | of one function or process into another.
               | 
               | In a GUI application, the contents of text fields and
               | other controls can go through a function and the output
               | used to update another text field.
               | 
               | The point is to think carefully about where to place
               | mutability into your architecture and not arbitrarily
               | scatter it everywhere.
        
             | fwip wrote:
             | It's moved toward the edges of your program. In a lot of
             | functional languages, places that can perform these effects
             | are marked explicitly.
             | 
             | For example, in Haskell, any function that can perform IO
             | has "IO" in the return type, so the "printLine" equivalent
             | is: "putStrLn :: String -> IO". (I'm simplifying a bit
             | here). The result is that you know that a function like
             | "getUserComments :: User -> [CommentId]" is only going to
             | do what it says on the tin - it won't go fetch data from a
             | database, print anything to a log, spawn new threads, etc.
             | 
             | It gives similar organizational/clarity benefits as
             | something like "hexagonal architecture," or a capabilities
             | system. By limiting the scope of what it's possible for a
             | given unit of code to do, it's faster to understand the
             | system and you can iterate more confidently with code you
             | can trust.
        
             | scott_w wrote:
             | > However, don't you still need to understand the entire
             | program as ultimately that's what you are trying to build.
             | 
             | Depends on what I'm trying to do. If what I'm trying to
             | handle is local to the code, then possibly not. If the
             | issue is what's going into the function, or what the return
             | value is doing, then I likely do need that wider context.
             | 
             | What pure-functional functions do allow is certainty the
             | only things that can change the behaviour of that function
             | are the inputs to that function.
        
             | bcrosby95 wrote:
             | It lets you refine when and where it happens more than
             | other methods of restricting state change, such as in
             | imperative OOP.
        
             | SatvikBeri wrote:
             | A pretty basic example: I write a lot of data pipelines in
             | Julia. Most of the functions don't mutate their arguments,
             | they receive some data and return some data. There are a
             | handful of exceptions, e.g. the functions that write data
             | to a db or file somewhere, or a few performance-sensitive
             | functions that mutate their inputs to avoid allocations.
             | These functions are clearly marked.
             | 
             | That means that 90% of the time, there's a big class of
             | behavior I just don't need to look for when
             | reading/debugging code. And if it's a bug related to state,
             | I can pretty quickly zoom in on a few possible places where
             | it might have happened.
        
         | rendaw wrote:
         | 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 the
         | wrong result. The second will produce an error if you try to
         | reorder the statements.
         | 
         | Another way to look at it is that in the first example, the 3rd
         | calculation doesn't have "x" as a dependency but rather "x in
         | the state where addition has already been completed" (i.e. it's
         | 3 different x's that all share the same name). Doing single
         | assignment is just making this explicit.
        
           | skeezyjefferson wrote:
           | whats the difference between immutable and constant, which
           | has been in use far longer? why are you calling it mutable?
        
             | Thorrez wrote:
             | Immutable and constant are the same. rendaw didn't use the
             | word mutable. One reason someone might use the word
             | "mutable" is that it's a succinct way of expressing an
             | idea. Alternative ways of expressing the same idea are
             | longer words (changeable, non-constant).
        
               | kgwxd wrote:
               | They aren't the same for object references. The reference
               | can't be changed, but the properties can.
        
               | a4isms wrote:
               | In languages like JavaScript, immutable and constant may
               | be theoretically the same thing, but in practice "const"
               | means a variable cannot be reassigned, while "immutable"
               | means a value cannot be mutated in place.
               | 
               | They are very, very different semantically, because const
               | is always local. Declaring something const has no effect
               | on what happens with the value bound to a const variable
               | anywhere else in the program. Whereas, immutability is a
               | global property: An immutable array, for example, can be
               | passed around and it will always be immutable.
               | 
               | JS has always hade 'freeze' as a kind of runtime
               | immutability, and tooling like TS can provide for
               | readonly types that provide immutability guarantees at
               | compile time.
        
               | everforward wrote:
               | Arrays are a very notable example here. You can append to
               | a const array in JS and TS, even in the same scope it was
               | declared const.
               | 
               | That's always felt very odd to me.
        
               | raddan wrote:
               | That's because in many languages there is a difference
               | between a stored reference being immutable and the
               | contents of the thing the reference points to being
               | immutable.
        
               | skeezyjefferson wrote:
               | but we already had the word variable for values that can
               | change. on both counts it seems redundant
        
             | inanutshellus wrote:
             | "Constant" implies a larger context.
             | 
             | As in - it's not very "constant" if you keep re-making it
             | in your loop, right?
             | 
             | Whereas "immutable" throws away that extra context and
             | means "whatever variable you have, for however long you
             | have it, it's unchangeable."
        
               | skeezyjefferson wrote:
               | > As in - it's not very "constant" if you keep re-making
               | it in your loop, right?
               | 
               | you cant change a constant though
        
               | veilrap wrote:
               | He's implying that the variable it's being defined within
               | the loop. So, constant, but repeatedly redefined.
        
               | ghurtado wrote:
               | That's the opposite of what any reasonable engineer means
               | by "constant".
        
               | fnordsensei wrote:
               | That's the point, you're just haggling about scopes now.
               | All the way from being new per program invocation to new
               | per loop.
               | 
               | Immutability doesn't have this connotation.
        
               | davrosthedalek wrote:
               | How? I think the same argument applies: If it's changing
               | from loop to loop, seems mutable to me.
        
               | fnordsensei wrote:
               | I think you're after something other than immutability
               | then.
               | 
               | You're allowed to rebind a var defined within a loop, it
               | doesn't mean that you can't hang on to the old value if
               | you need to.
               | 
               | With mutability, you actively can't hang on to the old
               | value, it'll change under your feet.
               | 
               | Maybe it makes more sense if you think about it like tail
               | recursion: you call a function and do some calculations,
               | and then you call the same function again, but with new
               | args.
               | 
               | This is allowed, and not the same as hammering a variable
               | in place.
        
               | Zambyte wrote:
               | I can give a specific example.                   for
               | (0..5) |i| {             i = i + 1;
               | std.debug.print("foo {}\n", .{i});         }
               | 
               | In this loop in Zig, the reassignment to i fails, because
               | i is a constant. However, i is a new constant bound to a
               | different value each iteration.
               | 
               | To potentially make it clearer that this is not mutation
               | of a constant between iterations, technically &i _could_
               | change between iterations, and the program would still be
               | correct. This is not true with a c-style for loop using
               | explicit mutation.
        
               | davrosthedalek wrote:
               | No? It has a lifetime of one loop duration, and is
               | constant during that duration. Seems perfectly fine to
               | me.
        
             | scott_w wrote:
             | 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.
        
             | munificent wrote:
             | "Constant" is ambiguous. Depending on who you ask, it can
             | mean either:
             | 
             | 1. A property known at compile time.
             | 
             | 2. A property that can't change after being initially
             | computed.
             | 
             | Many of the benefits of immutability accrue properties
             | whose values are only known at runtime but which are still
             | known to not change after that point.
        
           | jstimpfle wrote:
           | The immutable approach doesn't conflate the concepts of
           | place, time, and abstract identity, like in-place mutation
           | does.
           | 
           | In mutating models, typically abstract (mathematical /
           | conceptual) objects are modeled as memory locations. Which
           | means that object identity implies pointer identity. But
           | that's a problem when different versions of the same object
           | need to be maintained.
           | 
           | It's much easier when we represent object identity by
           | something other than pointer identity, such as (string) names
           | or 32-bit integer keys. Such representation allows us to
           | materialize us different versions (or even the same version)
           | of an object in multiple places, at the same time. This
           | allows us to concurrently read or write different versions of
           | the same abstract object. It's also an enabler for
           | serialization/deserialization. Not requiring an object to be
           | materialized in one particular place allows saving objects to
           | disk or sending them around.
        
             | SpaceNoodled wrote:
             | The hardware that these programs are running on store
             | objects in linear memory, so it doesn't not make sense to
             | treat it as such.
        
               | wtallis wrote:
               | DRAM is linear memory. Caches, less so. Register files
               | _really_ aren 't. CPUs spend rather a lot of transistors
               | and power to reconcile the reality of how they manipulate
               | data within the core against the external model of RAM in
               | a flat linear address space.
        
           | EastLondonCoder wrote:
           | I agree that the explicit timeline you get with immutability
           | is certainly helpful, but I also think its much easier to
           | understand the total state of a program. When an imperative
           | program runs you almost always have to reproduce a bug in
           | order to understate the state that caused it, fairly often in
           | Clojure you can actually deduct whats happening.
        
             | ryandv wrote:
             | That's right - immutability enables equational reasoning,
             | where it becomes possible to actually reason through a
             | program just by inspection and evaluation in one's head,
             | since the only context one needs to load is contained
             | within the function itself - not the entire trace, where
             | _anything_ along the thread of execution could factor into
             | your function 's output, since anybody can just mutate
             | anybody else's memory willy-nilly.
             | 
             | People jump ahead using AI to improve their reading
             | comprehension of source code, when there are still basic
             | practices of style, writing, & composition that for some
             | reason are yet to be widespread throughout the industry
             | despite already having a long standing tradition in
             | practice, alongside pretty firm grounding in academics.
        
               | adrianN wrote:
               | In theory it's certainly right that imperative programs
               | are harder to reason about. In practice programmers tend
               | to avoid writing the kind of program where anything can
               | happen.
        
               | ryandv wrote:
               | > In practice programmers tend to avoid writing the kind
               | of program where anything can happen.
               | 
               | My faith in this presumption dwindles every year. I
               | expect AI to only exacerbate the problem.
               | 
               | Since we are on the topic of Carmack, "everything that is
               | syntactically legal that the compiler will accept will
               | eventually wind up in your codebase." [0]
               | 
               | [0] https://www.youtube.com/watch?v=1PhArSujR_A&t=15m54s
        
           | ape4 wrote:
           | I would be nicer if you gave x1 and x2 meaningful names
        
             | catlifeonmars wrote:
             | What would those names be in this example?
        
               | ape4 wrote:
               | In a real application meaningful names are nearly always
               | possible, eg:                   const pi = 3.1415926
               | const 2pi = 2 * pi         const circumference = 2pi *
               | radius
        
               | tmtvl wrote:
               | Calling tau 2pi is the most cursed thing I've seen all
               | day. Appropriate for Halloween.
        
               | smrq wrote:
               | If you call a variable tau in production code then you're
               | being overly cute. I know what it means, because I watch
               | math YouTube for fun, but $future_maintainer in all
               | likelihood won't.
        
               | catlifeonmars wrote:
               | Agree in real life you can come up with meaningful names
               | (and should when the names are used far away from the
               | point of assignment), but it doesn't make sense for GPs
               | example, where the whole point was to talk about
               | assignments in the abstract.
        
           | Tarean wrote:
           | Sometimes keeping a fixed shape for the variable context
           | across the computation can make it easier to reason about
           | invariants, though.
           | 
           | Like, if you have a constraint is_even(x) that's really easy
           | to check in your head with some informal Floyd-Hoare logic.
           | 
           | And it scales to extracting code into helper functions and
           | multiple variables. If you must track which set of variables
           | form one context x1+y1, x2+y2, etc I find it much harder to
           | check the invariants in my head.
           | 
           | These 'fixed state shape' situations are where I'd grab a
           | state monad in Haskell and start thinking top-down in terms
           | of actions+invariants.
        
           | raincole wrote:
           | 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.
        
             | suspended_state wrote:
             | Or they got inspired by how this is done in OCaml, which
             | was the host language for the earliest versions of Rust.
             | Actually, this is a behaviour found in many FP languages.
             | Regarding OCaml, there was even a experimental version of
             | the REPL where one could access the different variables
             | carrying the same name using an ad-hoc syntax.
        
             | airstrike wrote:
             | 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.
        
             | wongarsu wrote:
             | 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::<i32>()        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<i32,
             | ParseIntError>, 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
        
               | ymyms wrote:
               | Another idiomatic pattern is using shadowing to transform
               | something using itself as input:
               | 
               | let x = Foo::new().stuff()?; let x =
               | Bar::new(x).other_stuff()?;
               | 
               | So with the math example and what the poster above said
               | about type changing, most rust code I write is something
               | like:
               | 
               | let x: plain_int = 7
               | 
               | let x: added_int = add(x, 3);
               | 
               | let x: divided_int = divide(x, 2);
               | 
               | where the function signatures would be fn add(foo:
               | plain_int, int); fn divide(bar: added_int, int);
               | 
               | and this can't be reordered without triggering a compiler
               | error.
        
               | waffletower wrote:
               | In a Clojure binding this is perfectly idiomatic, but
               | symbolically shared bindings are not shadowed, they are
               | immutably replaced. Mutability is certainly available,
               | but is explicit. And the type dynamism of Clojure is a
               | breath of fresh air for many applications despite the
               | evangelism of junior developers steeped in laboratory
               | Haskell projects at university. That being said, I have a
               | Clojure project where dynamic typing is throughly
               | exploited at a high level, allows for flexible use of
               | Clojure's rational math mixed with floating point (or one
               | or the other entirely), and for optimization deeper
               | within the architecture a Rust implementation via JVM JNI
               | is utilized for native performance, assuring homogenous
               | unboxed types are computed to make the overall
               | computation tractable. Have your cake and eat it too.
               | Types have their virtues, but not without their excesses.
        
           | alain94040 wrote:
           | That example is too simple for me to grasp it. How would you
           | code a function that iterates over an array to compute its
           | sum. No cheating with a built-in sum function. If you had to
           | code each addition, how would that work? Curious to learn (I
           | probably could google this or ask Claude to write me the
           | code).
        
             | supergarfield wrote:
             | Carmack gives updating in a loop as the one exception:
             | 
             | > You should strive to never reassign or update a variable
             | outside of true iterative calculations in loops.
             | 
             | If you want a completely immutable setup for this, you'd
             | likely have to use a recursive function. This pattern is
             | well supported and optimized in immutable languages like
             | the ML family, but is not super practical in a standard
             | imperative language. Something like                 def
             | sum(l):         if not l: return 0         return l[0] +
             | sum(l[1:])
             | 
             | Of course this is also mostly insensitive to ordering
             | guarantees (the compiler would be fine with the last line
             | being `return l[-1] + sum(l[:-1])`), but immutability can
             | remain useful in cases like this to ensure no concurrent
             | mutation of a given object, for instance.
        
               | bmacho wrote:
               | You don't have to use recursion, that is, you don't need
               | language support for it. Having first class (named)
               | functions is enough.
               | 
               | For example you can modify sum such that it doesn't
               | depend on itself, but it depends on a _function_ , which
               | it will receive as argument (and it will be itself).
               | 
               | Something like:                 def sum_(f, l):
               | if not l: return 0         return l[0] + f(f, l[1:])
               | def runreq(f, *args):         return f(f, *args)
               | print(runreq(sum_, [1,2,3]))
        
               | hermitdev wrote:
               | While your example of `sum` is a nice, pure function,
               | it'll unfortunately blow up in python on even moderately
               | sized inputs (we're talking thousands of elements, not
               | millions) due to lack of tail calls in Python (currently)
               | and the restrictions on recursion depth. The CPython
               | interpreter as of 3.14 [0] is now capable of using tail
               | calls in the interpreter itself, but it's not yet in
               | Python, proper.
               | 
               | [0]: https://docs.python.org/3/whatsnew/3.14.html#a-new-
               | type-of-i...
        
               | dragonwriter wrote:
               | Yeah, to _actually_ use tail-recursive patterns (except
               | for known-to-be-sharply-constrained problems) in Python
               | (or, at least, CPython), you need to use a library like
               | `tco`, because of the implementation limits. Of course
               | the many common recursive patterns can be cast as map,
               | filter, or reduce operations, and all three of those are
               | available as functions in Python 's core (the first two)
               | or stdlib (reduce).
               | 
               | Updating one or more variables in a loop naturally maps
               | to reduce with the updated variable(s) being (in the case
               | of more than one being fields of) the accumulator object.
        
           | zerd wrote:
           | It's funny that converting the first example to the second is
           | a common thing a compiler does, Static single assignment [0],
           | to make various optimizations easier to reason about.
           | 
           | [0] https://en.wikipedia.org/wiki/Static_single-
           | assignment_form
        
         | zelphirkalt wrote:
         | Made a similar experience with Scheme. I could tell people
         | whatever I wanted, they wouldn't really realize how much
         | cleaner and easier to test things could be, if we just used
         | functions instead of mutating things around. And since I was
         | the only one who had done projects in an FP language, and they
         | only used non-FP languages like Java, Python, JavaScript and
         | TypeScript before, they would continue to write things based on
         | needless mutation. The issue was also, that using Python it can
         | be hard to write functional style code in a readable way too.
         | Even JS seems to lend itself better to that. What's more is,
         | that one will probably find oneself hard pressed to find the
         | functional data structures one might want to use and needs to
         | work around recursion due to the limitations of those
         | languages.
         | 
         | I think it's simply the difference between the curious mind,
         | who explores stuff like Clojure off the job (or is very lucky
         | to get a Clojure job) and the 9 to 5 worker, who doesn't know
         | any better and has never experienced writing a FP codebase.
        
           | SoftTalker wrote:
           | JS is much more of a functional language than it was given
           | credit for a long time. It had first-class functions and
           | closures from day one if I'm not mistaken.
        
             | inopinatus wrote:
             | I'm fond of saying "JS is a Lisp". It's not a hill I'd
             | bother dying on, however.
        
         | ndr wrote:
         | Clojure also makes it very easy, it'd require too much
         | discipline to do such a thing in Python. Even Carmack, who I
         | think still does python mostly by himself instead of a team, is
         | having issues there.
        
           | MetaWhirledPeas wrote:
           | > it'd require too much discipline to do such a thing in
           | Python
           | 
           | Is Python that different from JavaScript? Because it's easy
           | in JavaScript. Just stop typing var and let, and start typing
           | const. When that causes a problem, figure out how to deal
           | with it. If all else fails: "Dear AI, how can I do this thing
           | while continuing to use const? I can't figure it out."
        
             | codethief wrote:
             | I agree that Python is not _too_ different and in general I
             | treat my Python variables as const. One thing, however,
             | where I resort to mutating variables more often than I 'd
             | like is when building lists & dictionaries. Lambdas in
             | Python have horrible DX (no multi-line, no type
             | annotations, bad type checker support even in obvious
             | cases), which is why the functional approach to build your
             | list, using map() and filter() is much more cumbersome than
             | in JS. As a result, whenever a list comprehension becomes
             | too long, you end up building your list the old-fashioned
             | way, using a for loop and the_list.append().
        
         | ErroneousBosh wrote:
         | I guess I'm not that good a programmer, because I don't really
         | understand why variables that can't be varied are useful, or
         | why you'd use that.
         | 
         | How do you write code that actually works?
        
           | mleo wrote:
           | It forces you to consider when, where and why a change occurs
           | and can help reason later about changes. Thread safety is a
           | big plus.
        
             | ErroneousBosh wrote:
             | Okay, so for example I might set something like "this bunch
             | of parameters" immutable, but "this 16kB or so of floats"
             | are just ordinary variables which change all the time?
             | 
             | Or then would the block of floats be "immutable but not
             | from this bit"? So the code that processes a block of
             | samples can write to it, the code that fills the sample
             | buffer can write to it, but nothing else should?
        
               | stickfigure wrote:
               | Sounds like you have a data structure like
               | `Array<Float>`. The immutable approach has methods on
               | Array like:                  Array<Float> append(Float
               | value);        Array<Float> replace(int index, Float
               | value);
               | 
               | The methods don't mutate the array, they return a new
               | array with the change.
               | 
               | The trick is: How do you make this fast without copying a
               | whole array?
               | 
               | Clojure includes a variety of collection classes that
               | "magically" make these operations fast, for a variety of
               | data types (lists, sets, maps, queues, etc). Also on the
               | JVM there's Vavr; if you dig around you might find
               | equivalents for other platforms.
               | 
               | No it won't be quite as fast as mutating a raw buffer,
               | but it's usually plenty fast enough and you can always
               | special-case performance sensitive spots.
               | 
               | Even if you never write a line of production Clojure,
               | it's worth experimenting with just to get into the
               | mindset. I don't use it, but I apply the principles I
               | learned from Clojure in all the other languages I do use.
        
               | ErroneousBosh wrote:
               | > The methods don't mutate the array, they return a new
               | array with the change.
               | 
               | But then I need to update a bunch of stuff to point to
               | the new array, and I've still got the old incorrect array
               | hanging around taking up space.
               | 
               | This just sounds like a great way to introduce bugs.
        
               | stickfigure wrote:
               | It ends up being quite the opposite - many, many bugs
               | come from unexpected side effects of mutation. You pass
               | that array to a function and it turns out 10 layers
               | deeper in the call stack, in code written by somebody
               | else, some function decided to mutate the array.
               | 
               | Immutability gives you solid contracts. A function takes
               | X as input and returns Y as output. This is predictable,
               | testable, and thread safe by default.
               | 
               | If you have a bunch of stuff pointing at an object and
               | all that stuff needs to change when the inner object
               | changes, then you "raise up" the immutability to a higher
               | level.                   Universe nextStateOfTheUniverse
               | = oldUniverse.modifyItSomehow();
               | 
               | If you keep going with this philosophy you end up with
               | something roughly like "software transactional memory"
               | where the state of the world changes at each step, and
               | you can go back and look at old states of the world if
               | you want.
               | 
               | Old states don't hang around if you don't keep references
               | to them. They get garbage collected.
        
               | ErroneousBosh wrote:
               | Okay, so this sounds like it's a method of programming
               | that is entirely incompatible with anything I work on.
               | 
               | What sort of thing would it be useful for?
               | 
               | The kind of things I do tend to have maybe several
               | hundred thousand floating point values that exist for
               | maybe a couple of hundred thousandths of a second, get
               | processed, get dealt with, and then are immediately
               | overwritten with the next batch.
               | 
               | I can't think of any reason why I'd ever need to know
               | what they were a few iterations back. That's gone, maybe
               | as much as a ten-thousandth of a second ago, which may as
               | well be last year.
        
           | jayd16 wrote:
           | If you need new values you just make new things.
           | 
           | If you want to do an operation on fooA, you don't mutate
           | fooA. You call fooB = MyFunc(fooA) and use fooB.
           | 
           | The nice thing here is you can pass around pointers to fooA
           | and never worry that anything is going to change it
           | underneath you.
           | 
           | You don't need to protect private variables because your
           | internal workings cannot be mutated. Other code can copy it
           | but not disrupt it.
        
             | MetaWhirledPeas wrote:
             | > If you need new values you just make new things. > If you
             | want to do an operation on fooA, you don't mutate fooA. You
             | call fooB = MyFunc(fooA) and use fooB.
             | 
             | The beautiful thing about this is you can stop naming
             | things generically, and can start naming them specifically
             | what they are. Comprehension goes through the roof.
        
               | ErroneousBosh wrote:
               | Yes, but you can do that without having loads of stale
               | copies of incorrect data lying around, presumably.
        
             | ErroneousBosh wrote:
             | > If you want to do an operation on fooA, you don't mutate
             | fooA. You call fooB = MyFunc(fooA) and use fooB.
             | 
             | This is the bit I don't get.
             | 
             | Why would I do that? I will never want a fooA and a fooB. I
             | can't see any circumstances where having a correct fooB and
             | an incorrect fooA kicking around would be useful.
        
               | jayd16 wrote:
               | As Carmack points out, naming the intermediate values
               | aides in debugging. It also helps you write code as you
               | can give a name to every mutation.
               | 
               | But also keep in mind that correct and incorrect is not
               | binary. You might want to pass a fooA to another class
               | that does not want the fooB mutation.
               | 
               | If you just have foo, you end up with situations where a
               | copy should have happened but didn't and then you get
               | unwanted changes.
        
               | ErroneousBosh wrote:
               | But that's just it, why would a copy ever happen? Why
               | would you want a correct and an incorrect version of your
               | variable hanging about?
        
               | takinola wrote:
               | It is about being able to think clearly about your code
               | logic. If your code has many places where a variable can
               | change, then it is hard to go back and understand exactly
               | where it changed if you have unexpected behavior. If the
               | variable can never change then the logical backtrace is
               | much shorter.
        
           | jimbokun wrote:
           | The concept is actually pretty simple: instead of changing
           | existing values, you create new values.
           | 
           | The classic example is a list or array. You don't add a value
           | to an existing list. You create a new list which consists of
           | the old list plus the new value. [1]
           | 
           | This is a subtle but important difference. It means any part
           | of your program with a reference to the original list will
           | not have it change unexpectedly. This eliminates a large
           | class of subtle bugs you no longer have to worry about.
           | 
           | [1] Whether the new list has completely new copy of the
           | existing data, or references it from the old list, is an
           | important optimization detail, but either way the guarantee
           | is the same. It's important to get these optimizations right
           | to make the efficiency of the language practical, but while
           | using the data structure you don't have to worry about those
           | details.
        
             | ErroneousBosh wrote:
             | > It means any part of your program with a reference to the
             | original list will not have it change unexpectedly.
             | 
             | I don't get why that would be useful. The old array of
             | floats is incorrect. Nothing should be using it.
             | 
             | That's the bit I don't really understand. If I have a list
             | and I do something to it that gives me another updated
             | list, why would I ever want anything to have the old
             | incorrect list?
        
               | b_e_n_t_o_n wrote:
               | State exists in time, a variable is usually valid at the
               | point it's created but it might not be valid in the
               | future. Thus if part of your program accesses a variable
               | expecting it to be from a certain point in time but it's
               | actually from another point in time (was mutated) that
               | can cause issues.
        
               | jimbokun wrote:
               | You pass in an array to a function meant to perform a
               | transformation on each item of the array and return the
               | result.
               | 
               | You pass in an array of 10 values.
               | 
               | While the function is executing, some other thread adds
               | two more values to the array.
               | 
               | How many values should the result of the function call
               | have? 10 or 12? How do you guarantee that is the case?
        
               | ErroneousBosh wrote:
               | > While the function is executing, some other thread adds
               | two more values to the array.
               | 
               | This is not something that can happen.
        
         | rafaelmn wrote:
         | I would say it's more than immutability - it's the "feel" of
         | working with values. I've worked with at least 6 languages
         | professionally and likely more for personal projects over last
         | 20 years. I can say that Clojure was the most impactful
         | language I learned.
         | 
         | I tried to learn Haskel before but I just got bogged down in
         | the type system and formalization - that never sat with me
         | (ironically in retrospect Monads are a trivial concept that
         | they obfuscated in the community to oblivion, yet another Monad
         | tutorial was a meme at the time).
         | 
         | I used F# as well but it is too multi paradigm and pragmatic, I
         | literally wrote C# in F# syntax when I hit a wall and I didn't
         | learn as much about FP when I played with it.
         | 
         | Clojure had the lisp weirdness to get over, but it's
         | homoiconicty combined with the powerful semantics of core data
         | structures - it was the first time where the concept of working
         | with values vs objects 'clicked' for me. I would still never
         | use it professionally, but I would recommend it to everyone who
         | does not have a background in FP and/or lisp experience.
        
           | MarkMarine wrote:
           | I have dreams of being at a "Clojure shop" but I fear daily
           | professional use might dull my love for the language. Having
           | to realize that not everyone on my team wants to learn lisp
           | (or FP) just to work with my code (something I find amazing
           | and would love to be paid to do) was hard.
           | 
           | On a positive note I have taken those lessons from clojure
           | (using values, just use maps, Rich's simplicity, functional
           | programming without excessive type system abstraction, etc)
           | and applied them to the rest of my programming when I can and
           | I think it makes my code much better.
        
         | StopDisinfo910 wrote:
         | I think the advantage is often oversold and people often miss
         | how things actually exist on a continuum and just plainly
         | opposing mutable and immutable is sidestepping a lot of
         | complexity.
         | 
         | For exemple, it's endlessly amusing to me to see all the
         | efforts the Haskell community does to basically reinvent
         | mutability in a way which is somehow palatable to their type
         | system. Sometimes they even fail to even realise that it's what
         | they are doing.
         | 
         | In the end, the goal is always the same: better control and
         | warranties about the impact of side effects with minimum fuss.
         | Carmack approach here is sensible. You want practices which
         | make things easy to debug and reason about while mainting
         | flexibility where it makes sense like iterative calculations.
        
           | eyelidlessness wrote:
           | > Sometimes they even fail to even realise that it's what
           | they are doing.
           | 
           | Because that's not what they're doing. They're _isolating_
           | state in a systemic, predictable way.
        
             | StopDisinfo910 wrote:
             | 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 way to easily track side
             | effects after all, not banish them.
        
               | eyelidlessness wrote:
               | 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.
        
           | pxc wrote:
           | If you read through the Big Red Book1 or its counterpart for
           | Kotlin2, 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" functional languages like Haskell, there
           | are languages that are sort of immutability-first (and
           | support sophisticated effects libraries), or at least have
           | good immutable collections libraries in the stdlib, but are
           | flexible about mutation as well, so you can pick your poison:
           | Scala, Clojure, Rust, Nim (and probably lots of others).
           | 
           | All of these go further and are more comfortable than just
           | throwing `const` or `.freeze` around in languages that
           | weren't designed with this style in mind. If you haven't
           | tried them, you should! They're really pleasant to work with.
           | 
           | ----
           | 
           | 1: https://www.manning.com/books/functional-programming-in-
           | scal...
           | 
           | 2: https://www.manning.com/books/functional-programming-in-
           | kotl...
        
             | MetaWhirledPeas wrote:
             | > 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!_.
        
           | Maxatar wrote:
           | >For exemple, it's endlessly amusing to me to see all the
           | efforts the Haskell community does to basically reinvent
           | mutability in a way which is somehow palatable to their type
           | system.
           | 
           | That's because Haskell is a predominantly a research language
           | originally intended for experimenting with new programming
           | language ideas.
           | 
           | It should not be surprising that people use it to come up
           | with or iterate on existing features.
        
         | dwwoelfel wrote:
         | 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).
        
           | didibus wrote:
           | 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.
        
           | manoDev wrote:
           | 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.
        
         | ratelimitsteve wrote:
         | the flash of enlightment I had when I understood the incredible
         | power the rules of functional programming give you as a coder
         | is probably the biggest one I've had in my career so far.
         | idempotence, immutability and statelessness on their own let
         | you build a thing once in a disciplined way and then use it all
         | willy nilly anywhere you want without having to think about
         | anything other than "things go into process, other things come
         | out" and it's _so_ nice.
        
         | m463 wrote:
         | I think a lot of this kind of stuff should have language
         | support (like he mentions), even if it is not that functional
         | and is just as a hint.
         | 
         | That said, utopias are not always a great idea. Making all your
         | code functional might be philosophically satisfying, but
         | sometimes there are good reasons to break the rules.
        
       | Havoc wrote:
       | Good point. Had never occurred to me that keeping steps help
       | debug. Obvious in hindsight
        
         | Sammi wrote:
         | For the same reason almost all my functions end with this:
         | const result = ... ;       return result;
         | 
         | I know debuggers can show return values, but that doesn't help
         | when you're just doing a quick console log inspection.
        
       | ardit33 wrote:
       | Meh... I agree where he comes from (when working on large
       | projects, muttability can introduce bugs), but many languages
       | have solved it, similar by having two types, 'let' and 'var', or
       | 'const' in front of a variable.
       | 
       | But, there is practicality in the ability of being able to change
       | a var, and not having to create a new object every time you
       | change one of its members.
       | 
       | It models real nature/physics better.
       | 
       | It looks like He is asking that 'const' be the default, and 'var'
       | should be explicit, which makes sense.
        
       | or_am_i wrote:
       | In JetBrains editors it's possible to highlight mutable
       | variables, at least in the languages where the distinction
       | exists. My go to setting in Kotlin is to underscore all `var`'s,
       | for two reasons:
       | 
       | - this makes them really stand out, much easier to track the
       | mutation visually,
       | 
       | - the underscore effect is intrusive just-enough to nudge you to
       | think twice when adding a new `var`.
       | 
       | Nothing like diving into a >3k lines PR peppered with
       | underscores.
        
       | Razengan wrote:
       | If designing your hypothetical ideal language, what are some
       | intuitive/cute keywords you would choose for mutables/immutables?
       | 
       | `let` is so 2020. `const` is too long. `static` makes me fall
       | asleep at the keyboard. `con` sounds bad. How about
       | 
       | `law`?                   law pi = 3.142 (heh typing on Mac
       | autocompleted this)              law c = 29979245
       | law law = "Dredd"
       | 
       | or `set` or `once` or `make`?
        
         | tomaytotomato wrote:
         | There was a proposal in Java a few years back to introduce
         | "val".
         | 
         | I think it never gained traction but it would have been nice to
         | have this in Java                   val firstName = "Bob";
         | val lastName = "Tables";         var fullName = "";
         | fullName = firstName + " " + lastName;
        
           | mcdeltat wrote:
           | Wouldn't this "val" be the same as "final"?
           | 
           | Also related, it annoys me that Java has final but otherwise
           | poor/leaky support for immutability. You can mark something
           | final but most Java code (and a lot of the standard library)
           | uses mutable objects so the final does basically nothing...
           | C++ "const" desparately needs to spread to other languages.
        
           | Razengan wrote:
           | That's an aesthetically awkward and also bug-prone syntax: So
           | just a difference of 1 single letter (that looks similar) to
           | mean the completely opposite thing?? Nah you don't want that,
           | and I don't either.
        
             | nvlled wrote:
             | Kotlin uses var/val too[0] which is what Java is trying to
             | copy. I have never written any kotlin code before, so I
             | don't know if this would be a problem in practice. On the
             | plus side, var and val both have the same length, so the
             | variable declaractions are properly aligned. The names are
             | also intuitive as far as I can tell.In theory, I'd probably
             | be okay with it.
             | 
             | [0] https://kotlinlang.org/docs/basic-syntax.html#variables
        
               | ahoka wrote:
               | Not a problem in practice as you use val 99.99% percent
               | of the cases (which shows why immutability should be the
               | default, because most often that is needed) and Idea
               | underlines any mutable references, so the sticks out. It
               | also suggests val when a var is not actually mutated.
        
             | dionian wrote:
             | They're intuitively named. A value is a value. A variable
             | is a variable.
        
               | Razengan wrote:
               | A variable is also a value.
        
         | veltas wrote:
         | What is wrong with let?
        
           | Razengan wrote:
           | Nothing, it's just been done, just trying to think of some
           | better/newer ways to say it :)
        
       | considerdevs wrote:
       | This is the kind of wisdom that comes after hours of debugging
       | and discovering the bug was your own variable reuse.
       | 
       | Wouldn't this be an easy task for SCA tool e.g. Pylint? It has
       | atleast warning against variable redefinition:
       | https://pylint.pycqa.org/en/latest/user_guide/messages/refac...
        
         | maleldil wrote:
         | This is only for redefinitions that change the type. If you re-
         | assign with the same type, there's no warning. However, pylint
         | does issue warnings for other interesting cases, such as
         | redefining function arguments and variables from an outer
         | scope.
        
       | MrNet32823 wrote:
       | Jonathan Blow had strong objection with const keyword. I forgot
       | because i did not understand at that time. Does anyone with jai
       | experience have a counter point to that.
        
         | dude250711 wrote:
         | He also stopped shipping things.
        
         | JonChesterfield wrote:
         | I believe the standard counterargument goes:
         | 
         | - either it's transitive, in which case your type system is
         | very much more complicated
         | 
         | - or it isn't, in which case it's a near useless liability
         | 
         | Naturally C++ runs with the latter, with bonus extra typing for
         | all the overloads it induces.
        
           | maleldil wrote:
           | How isn't it transitive in C++? If the variable/reference is
           | const, you can't modify fields, and you can only call const
           | methods. What else do you need?
        
       | sunrunner wrote:
       | I like the idea of immutable-by-default, and in my own musings on
       | this I've imagined a similar thing except that instead of a
       | mutable keyword you'd have something more akin to Python's with
       | blocks, something like:                   # Immutable by default
       | x = 2         items = [1,2,3]              with mutable(x,
       | items):             x = 3             items.append(4)
       | # And now back to being immutable, these would error         x =
       | 5         items.append(6)
       | 
       | I have put almost zero thought into the practicality of this for
       | implementation, the ergonomics for developers, and whether it
       | would be different enough and useful enough to be worth having.
        
         | pizza234 wrote:
         | This is in essence a mutable borrow - by looking at Rust's
         | borrow checker, one can see the complexities of the concept.
        
           | danenania wrote:
           | Clojure has transients--a similar idea I believe. Basically
           | bounded mutation.
        
         | unrealhoang wrote:
         | Without a borrowck, inside your mutable block, another variable
         | can reference to the mutable version of your x or items, and be
         | mutated outside of that block.
        
           | teo_zero wrote:
           | No if you're allowed to only get an immutable reference from
           | an immutable variable.
        
       | DonHopkins wrote:
       | Did you ever have one of those days when variables won't and
       | constants aren't?
        
       | koolba wrote:
       | This is the mental distinction of what something represents vs
       | what is its value. The former should never change regardless of
       | mutability (for any sane program...), and the latter would never
       | change for a const declaration.
       | 
       | The value (pun intended) of the latter is that once you've
       | arrived at a concrete result, you do not have to think about it
       | again.
       | 
       | You're not defining a "variable", you're naming an intermediate
       | result.
        
       | smallstepforman wrote:
       | There are languages nobody uses, and languages people complain
       | about. Computing is about change, otherwise there is nothing to
       | compute. The mere fact that its called a "variable" makes it
       | obvious that its supposed to change.
        
         | butokai wrote:
         | This is a viewpoint commonly held by students who were exposed
         | to imperative programming before having any class in maths.
         | However it shouldn't survive long after that.
        
         | tialaramex wrote:
         | Bjarne's excuse is very silly, it's like the Laffer curve but
         | for programming language defects. It pins one edge case nobody
         | cares about, then tries to imply that's proof for a claim no
         | sane person could agree with and for which there is no
         | evidence. Bjarne says languages with no users attract no
         | complaints regardless of how terrible they are (nobody was
         | arguing that they do), therefore implies Bjarne, the fact that
         | people complain about my language just means it is popular.
         | Bzzt, wrong. They're complaining because it's so riddled with
         | problems.
         | 
         | Variables are distinct from _constants_. It 's a problem that C
         | and C++ use the keyword "const" to signify immutability
         | instead, indeed as a result C++ needed three more keywords
         | "constexpr", "constinit" and "consteval" to try to grapple with
         | the problem.
        
       | sgarland wrote:
       | Dumb question from a primarily Python programmer who mostly
       | writes (sometimes lengthy) scripts: if you have a function doing
       | multiple API calls - say, to different AWS endpoints with boto3 -
       | would you be expected to have a different variable for each
       | response? Or do you delete the variable after it's handled, so
       | the next one is "new?"
        
         | TZubiri wrote:
         | I think renaming an old variable is a common and sensible way
         | to free a resource in python. If there are no valid names for a
         | resource it will be garbage collected. Which is different in
         | languages like C++ with manual memory management.
         | 
         | John Carmack is a C++ programmer apparently that still has a
         | lot to learn in python.
        
           | sswatson wrote:
           | 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
           | speaking from a thorough knowledge of the arguments for and
           | against.
        
           | ForHackernews wrote:
           | But wouldn't you do that inside a function or a loop body?
        
             | busfahrer wrote:
             | In TFT, he mentions
             | 
             | > [...] outside of true iterative calculations
        
           | maleldil wrote:
           | Not "a resource", but memory specifically. If there's a
           | proper resource (e.g. a file), you should ensure it's
           | explicitly released instead of relying on the GC (using
           | with/close/etc.) And if memory usage is really important, you
           | should probably explicitly delete the variable.
           | 
           | Anything else is wishful thinking, trying to rely on the GC
           | for deterministic behaviour.
        
           | blueside wrote:
           | if this guys learns enough, who knows, he may have a future
           | in programming!
        
         | ForHackernews wrote:
         | If they're representing different data from different API
         | calls, yeah, I'd be strongly inclined to give them different
         | names.                   order_data = boto.get_from_dynamodb()
         | customer_data = boto.get_from_rds()         branding_assets =
         | boto.get_from_s3()         return render_for_user(order_data,
         | customer_data, branding_assets, ...)
        
       | jodleif wrote:
       | This Carmack guy knows his stuff.
        
       | kybernetyk wrote:
       | the comments/replies to his tweet remind me why I usually avoid
       | twitter
        
       | KaiserPro wrote:
       | I don't mean to start a holy war, thats not the point, but isn't
       | this a side effect of C++ footguns rather than python allowing
       | you to be lazy?
       | 
       | I mean there is good reason to keep variables well scoped, and
       | the various linters do a reasonable job about scope.
       | 
       | But I've only really know C++[1] people to want everything as a
       | const.
       | 
       | [1] Yes, you functional people also, but, lets not get into that.
        
       | cfontes wrote:
       | He will like RUST very much.
        
         | galangalalgol wrote:
         | Carmack had said he likes rust, he just isn't a language
         | zealot. In rust though, the idiom would be to shadow
         | intermediate variables often, which removes the debugger
         | benefit.
        
           | metaltyphoon wrote:
           | > the idiom would be to shadow intermediate variables often
           | 
           | There is no idiom about this. Do it if you like but clipply
           | doesn't warn about any of it.
        
             | galangalalgol wrote:
             | There isn't an enforced one, and the opinion is bifurcated,
             | but I regularly find crates that clearly treat this as
             | idiomatic practice.
        
       | mavhc wrote:
       | Really should invent a new name if you don't want your variables
       | to vary
        
       | agentultra wrote:
       | Agree. After working seriously on a large production Haskell
       | codebase for several years I definitely took it for granted. Now
       | that I'm writing stuff in C again I do think immutability should
       | be the default.
       | 
       |  _const_ isn't really it though. It could go further.
        
         | nixpulvis wrote:
         | Are Rust's defaults far enough?
        
         | 1718627440 wrote:
         | Well in C actually you can not mutate something, you can only
         | reassign, as it is always pass-by-value. You need to work
         | around that, by passing a pointer to the object instead. In
         | that sense mutability is kind of a language keyword: '&'. When
         | you want to just get the object, you pass object it, if you
         | need to modify it, you need to pass &object. This is something
         | I hate in C++, that random function invocations can mutate
         | arguments without it being obvious in the call syntax.
        
           | astrobe_ wrote:
           | I think that's why the * is generally preferred over the &
           | for this purpose. It also can give some hints about ownership
           | issues. This "pass by reference" thing is syntactic sugar and
           | sometimes is great to have, but as Perlis said, "Syntactic
           | sugar causes cancer of the semicolon" [1].
           | 
           | [1] https://www.cs.yale.edu/homes/perlis-alan/quotes.html
        
       | theodorethomas wrote:
       | Do as much as you can in a spreadsheet, then start a new
       | spreadsheet.
        
       | Const-me wrote:
       | For performance critical code, you want to reuse L1D cache lines
       | as much as possible. In many cases, allocation of a new immutable
       | object boils down to malloc(). Newly allocated memory is unlikely
       | to be found on L1D cache. OTOH, replacing data in recently
       | accessed memory and reusing the memory is very likely to become
       | L1D cache hit in runtime.
        
         | ramses0 wrote:
         | "Immutability" from a programming language statement
         | perspective doesn't necessarily imply that the implementation
         | duplicates memory or variables.
         | 
         | Similar to how "tail recursion can (usually) be lifted/lowered
         | to a simple loop...", immutability from language statements can
         | often be "collapsed" into mutating a single variable, and there
         | may be one or two "dances" you need to do to either add helper
         | functions, structure your code _slightly_ differently to get
         | there, but it's similar to any kind of performance sensitive
         | code.
         | 
         | Example foo(bar(baz(), bar_alt(baz_alt(), etc...))) [where
         | function call nesting is "representing" an immutability graph]
         | ...yeah, that'd have a lot of allocations and whatever.
         | 
         | But: foo().bar().bar_alt().baz().baz_alt().etc(...) you could
         | imagine is always just stacking/mutating the same variable[s]
         | "in place".
         | 
         | ...don't get hung up on the syntax (it's wildly wrong), but
         | imagine the concept. If all the functions "in the chain" are
         | pure (no globals, no modifications), then they can be analyzed
         | and reduced. Refer back to the "Why SSA?" article from a week
         | or two ago: https://news.ycombinator.com/item?id=45674568
         | ...and you'll see how the logical lines of statements don't
         | necessarily correspond to the physical movement of memory and
         | registers.
        
           | Const-me wrote:
           | You're describing an edge case. Generally speaking, memory is
           | only reused after old objects are deallocated. And here's the
           | relevant quote from the OP's post:
           | 
           | > Having all the intermediate calculations still available is
           | helpful in the debugger
        
         | marginalia_nu wrote:
         | For performance critical code, you wouldn't use
         | malloc()-allocation at all, though whether using an arena
         | allocator or putting stuff on the stack, your argument is still
         | sane. Data locality is speed.
        
       | xd1936 wrote:
       | Wouldn't this allocate wasteful amounts of RAM unnecessarily for
       | every step in a calculation?
        
         | beeflet wrote:
         | not if it gets optimized out
        
       | duxup wrote:
       | I'm going to maybe out myself as having limited experience here
       | ...
       | 
       | I don't mind the idea here, seems good. But I also don't move a
       | block of code often and discover variable assignment related
       | issues.
       | 
       | Is the bad outcome more often seen in C/C++ or specific use
       | cases?
       | 
       | Granted my coding style doesn't tend to involve a lot of
       | variables being reassigned or used across vast swaths of code
       | either so maybe I'm just doing this thing and so that's why I
       | don't run into it.
        
         | Galanwe wrote:
         | So essentially he gives 2 arguments:
         | 
         | 1) You get intermediate results visible in the debugger /
         | accessible for logs, which I think is a benefit in any
         | language.
         | 
         | 2) You get an increased safety in case you move around some
         | code. I do think that it applies to any language, maybe
         | slightly more so in C due to its procedural nature.
         | 
         | See, the following pattern is rather common in C (not so much
         | in C++):
         | 
         | - You allocate a structure S
         | 
         | - If S is used as input, you prefill it with some values. If
         | it's used as output, you can keep it uninitialized or zfill.
         | 
         | - You call function f providing a pointer to that struct.
         | 
         | Lots of C APIs work that way: sockets addresses, time
         | structures, filesystem entries, or even just stack allocated
         | fixed size strings are common.
        
         | d0mine wrote:
         | In Python, no user object is modified by a simple assignment to
         | a name. It just binds it.
         | 
         | It is not about mutable/immutable objects , it is about using a
         | name for a single purpose within given scope.
         | a = 1         b = 2         a = b
         | 
         | "a" name refers to the "2" object. "1" hasn't changed (ints are
         | immutable in Python). If nothing else references it, it might
         | as well disappear (or not).
         | 
         | Though both "single purpose" and immutability may be [distinct]
         | good ideas.
        
       | reverseblade2 wrote:
       | Use F#, compile it to .net, rust, JavaScript, typescript and
       | python. Problem solved.
        
       | mcv wrote:
       | I use Javscript mostly. Or Typescript actually, these days. I
       | remember when ES2015 introduced `let` because `var` had weird
       | scoping issues. But ever since, I barely use either of them.
       | Everything is `const` these days, as it should.
        
         | andsoitis wrote:
         | > I use Javscript mostly. Or Typescript actually, these days. I
         | remember when ES2015 introduced `let` because `var` had weird
         | scoping issues. But ever since, I barely use either of them.
         | Everything is `const` these days, as it should. reply
         | 
         |  _const_ prevents reassignment of the variable but it does not
         | make the object the variable points to immutable.
         | 
         | To do the latter, you have to use Object.freeze (prevent
         | modification of an object's properties, but it is shallow only
         | so for nested objects you need to recurse) and Object.seal
         | (prevent adding or removing properties, but not changing them).
         | 
         | May people use immutable.js or Immer for ergonomic immutable
         | data structures.
        
           | mcv wrote:
           | That is an excellent point, and indeed a problem when
           | debugging. When I log objects to the console, often they
           | don't get serialized until I actually click on them, which
           | means I don't get to see the object as it was at the time,
           | but after a bunch of later changes.
        
         | rlander wrote:
         | Except const is not sufficient. It will prevent the reference
         | from being reassigned but the const can still reference a
         | mutable object.
        
         | LogicHound wrote:
         | `var` doesn't have weird scoping issues, it just different than
         | other languages. `var` is function scoped, thus all var
         | declarations are hoisted to the top of the function during
         | execution.
         | 
         | This is why the single var pattern used to be recommended.
        
       | hackthemack wrote:
       | One area that I like to have immutability is in function argument
       | passing. In javascript (and many other languages), I find it
       | weird that arguments in function act differently depending on if
       | they are simple (strings, numbers) versus if they are complex
       | (objects, arrays).
       | 
       | I want everything that passes through a function to be a copy
       | unless I put in a symbol or keyword that it suppose to be passed
       | by reference.
       | 
       | I made a little function to do deep copies but am still
       | experimenting with it.                 function deepCopy(value) {
       | if (typeof structuredClone === 'function') {           try {
       | return structuredClone(value); } catch (_) {}         }
       | try {           return JSON.parse(JSON.stringify(value));
       | } catch (_) {           // Last fallback: return original
       | (shallow)           return value;         }       }
        
         | jstimpfle wrote:
         | Problem is that "copy" of an object is not well defined. It
         | could be a "shallow" copy or a "deep" copy. The only models
         | where "copy" is defined are simple "memory"/"value" models
         | (like C) and immutable models (like Haskell).
        
         | hackthemack wrote:
         | It is a similar idea to what Carmack is writing about. Golang,
         | Clojure does something similar to what I am talking about above
         | so not sure of the motivation behind the voting down of the
         | comment.
        
         | DougBTX wrote:
         | > I want everything that passes through a function to be a copy
         | unless I put in a symbol or keyword that it suppose to be
         | passed by reference.
         | 
         | JavaScript doesn't have references, it is clearer to only use
         | "passed by reference" terminology when writing about code in a
         | language which does have them, like C++ [0].
         | 
         | In JavaScript, if a mutable object is passed to a function,
         | then the function can change the properties on the object, but
         | it is always the same object. When an object is passed by
         | reference, the function can replace the initial object with a
         | completely different one, that isn't possible in JS.
         | 
         | Better is to distinguish between immutable objects (ints,
         | strings in JS) and mutable ones. A mutable object can be made
         | immutable in JS using Object.freeze [1].
         | 
         | [0] https://en.wikipedia.org/wiki/Reference_(C%2B%2B)
         | 
         | [1] https://developer.mozilla.org/en-
         | US/docs/Web/JavaScript/Refe...
        
           | hackthemack wrote:
           | I guess in javascript world, the phrasing I am looking for
           | would be
           | 
           | I wish all arguments were copies unless I put some symbol
           | that says, alright, go ahead and give me the original to
           | mutate?
           | 
           | It seems like this way, you reduce side effects, and if you
           | want the speed of just using the originals, you could still
           | do that by using special notation.
        
           | hackthemack wrote:
           | Thinking on this subject a bit more. I have to wonder if C++
           | made references just so there is a fast an efficient way to
           | mutate without having to make a copy?
           | 
           | Maybe someone should work on a way to make references in
           | javascript land. Sort of like immer but baked in.
           | 
           | https://immerjs.github.io/immer/
        
       | oulipo2 wrote:
       | Can we stop linking the racist website that twitter has become?
        
       | AaronAPU wrote:
       | This would require coming up with an order of magnitude more
       | variable names which is just unnecessary cognitive load.
        
         | jstimpfle wrote:
         | Either you don't understand what you're talking about, or
         | you've missed the word "strive" in the tweet.
        
         | chongli wrote:
         | No, not at all. You still have the advantages of scopes, name
         | shadowing, namespaces, and collection types. If your language
         | supports them, you can also use algebraic data types to further
         | reduce the number of names you need to deal with.
        
         | furyofantares wrote:
         | An order of magnitude? That sounds like pretty outrageous
         | hyperbole. A variable getting reassigned 10 times sounds
         | extremely rare, the average in my experience has to be less
         | than 1 reassignment. I think the approach requires coming up
         | with maybe 10% more names.
         | 
         | Usually there are good, obvious names for intermediate
         | calculations in my experience.
         | 
         | I'm open though - what kinds of things are you doing that
         | require reassigning variables so much?
        
           | AaronAPU wrote:
           | Probably exaggerated a bit with that phrasing ("outrageous"
           | seems similarly hyperbolic ;))
           | 
           | But any variable which I've not already marked as const is
           | pretty much by definition going to be modified at least once.
           | So now instead of 1 variable name you need at least two.
           | 
           | So now the average number of variables per non-const variable
           | is >= 2 and will be much more if you're doing for example DSP
           | related code or other math heavy code.
           | 
           | You can avoid it with long expressions but that in principle
           | is going against the "name every permutation" intention
           | anyway.
        
             | furyofantares wrote:
             | Fair enough re: "outrageous"!
             | 
             | It's actually math heavy code (or maybe medium heavy?)
             | where I really like naming every intermediate. fov,
             | tan_fov, half_tan_fov, center_x, norm_x
        
               | AaronAPU wrote:
               | I spent a decade or so working on video codecs with an
               | international team, and there was sort of an unwritten
               | rule that code shouldn't have comments and variable names
               | shouldn't be descriptive (english language couldn't be
               | assumed).
               | 
               | Which sounds really awful, but after a while it forces
               | you to parse the logic itself instead of being guided by
               | possibly-out-of-date comments and variable names.
               | 
               | I now prefer less verbosity so that probably explains why
               | I'm a little out of distribution on this topic.
               | 
               | If you looked at any of my code prior to that job, it was
               | the polar opposite with very "pretty" code and lengthy
               | comments everywhere.
        
       | bmitc wrote:
       | > I wish it was the default, and mutable was a keyword.
       | 
       | Well yea, that's what sane languages that aren't Python, C, and
       | C++ do. See F# and Rust.
        
       | nyrp wrote:
       | Is he referring to something specific with "true" iterative
       | calculations vs. plain old iterative ones, assuming they are in
       | some way "non-true" or less "true"? Like, avoiding i+=x in favor
       | of ++i or something? Or maybe am I just tired today.
        
         | ufo wrote:
         | I think he's just saying that mutation is ok if it's something
         | loopy, like changing the loop counter or updating some running
         | sum. So both i+=1 and ++i are fine.
        
       | nixpulvis wrote:
       | 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 for the
       | intermediate result, I'll give it that, but I'm not over here
       | naming things `result_without_processing`. You can read the code.
        
         | furyofantares wrote:
         | 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.
        
           | nixpulvis wrote:
           | I mean, I use `result` in a function named `generate` within
           | a class `JSON < Generator`. Stuff like this is pretty common.
        
             | philipov wrote:
             | if you're already committing to generic names, what's wrong
             | with a name like `processed_result`?
        
               | snarfy wrote:
               | In the flow he describes you end up with
               | processed_processed_processed_result.
        
               | WhyNotHugo wrote:
               | Java mentioned!
        
               | strbean wrote:
               | AbstractFactoryResultFactoryProcessedResultProcessedResul
               | tProcessorBeanFactory
        
               | throwway120385 wrote:
               | I think what they're getting at is that they sometimes
               | use composition of functions in places where other people
               | might call the underlying functions as one procedure and
               | have intermediate results.
               | 
               | At the end of the day, you're all saying different ways
               | of keeping track of the intermediate results. Composition
               | just has you drop the intermediate results when they're
               | no longer relevant. And you can decompose if you want the
               | intermediates.
        
               | catlifeonmars wrote:
               | That name is kind of redundant, since `result` implies
               | `processed` in the first place.
        
             | MetaWhirledPeas wrote:
             | > Stuff like this is pretty common.
             | 
             | Common != Good
        
           | eugenekolo wrote:
           | I disagree you'd find "obvious" non-generic names easily.
           | After all, "naming" is one of the hardest things in computer
           | science.
        
         | nailer wrote:
         | result.process()
         | 
         | That doesn't make logical sense. You already have a result. It
         | shouldn't need processing to be a result.
        
           | riskable wrote:
           | It also doesn't make sense for `process()` to be an attribute
           | of `result`. Why would you instantiate a class and call it
           | result!?
        
             | Bayko wrote:
             | 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.
        
               | nixpulvis wrote:
               | 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.
        
               | nomel wrote:
               | Depends on how clear it is.
               | 
               | I usually write code to help _local_ debug-ability (which
               | seems rare). For example, this allows one to _trivially_
               | set a conditional breakpoint and look into the full
               | response:                   response = get_response()
               | response = response.json()
               | 
               | The fact that the first response is immediately
               | overwritten proves to the reader it's not important/never
               | used, so they can forget about it, where a temp variable
               | would add cognitive load since it might be used later.
               | 
               | and I think is just as clear as this:
               | response = get_response().json()
               | 
               | This motivated by years of watching people through code,
               | and me working with deeply non-software engineers, and is
               | always very much appreciated.
        
             | feoren wrote:
             | > Why would you instantiate a class and call it result!?
             | 
             | Are you suggesting that the results of calculations should
             | always be some sort of primitive value? It's not clear what
             | you're getting hung up on here.
        
         | jimbokun wrote:
         | 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.
        
           | nixpulvis wrote:
           | Yea, very true. Not every language makes this nice though.
        
         | MetaWhirledPeas wrote:
         | > result.process()
         | 
         |  _What result? What process?_
         | 
         | ...says every person who has to read your code later.
        
           | b_e_n_t_o_n wrote:
           | I mean, it's probably pretty clear when you look at result's
           | initial assignment...
        
         | swid wrote:
         | I'm going to ignore the actual names used here - you can use
         | any name you want. I think this pattern is vulnerable to
         | introducing bugs that allow security bugs. I'm imagining
         | process being some kind of sanitization or validation. Then,
         | you have this thing called result, and some of the time it
         | might be "safe" or processed, and sometimes not. Sometimes
         | people will process it more or less than once with real
         | consequences.
         | 
         | So yeah, definitely it is much better to name the first one in
         | a way that makes it more clear it hasn't been processed yet.
        
       | anymouse123456 wrote:
       | 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?
        
         | tialaramex wrote:
         | 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 (for this piece of code) always saying
         | $10. That's not a variable it's a constant. In Rust we'd use
         | `const` for this but in C you need to use the C pre-processor
         | language instead to make constants, which is kinda wild.
         | 
         | However for each time this function runs we do also need to get
         | the customer ID. The customer ID will _vary_ each time this
         | function runs, as different customers check out their
         | purchases, but it does not _mutate_ during function execution
         | like that total earlier, in Rust these variables don 't need an
         | annotation, this is the default. In C you'd ideally want to
         | label these "const" which is the confusing name C gives to
         | immutable variables.
        
           | ajross wrote:
           | > In the cases we're interested in here the variable does
           | vary, what it doesn't do is mutate.
           | 
           | Those are synonyms, and this amounts to a retcon. The
           | computer science term "variable" comes directly from standard
           | mathematical function notation, where a variable reflects a
           | quantity being related by the function to other variables. It
           | absolutely is expected to "change", if not across "time" than
           | across the domain of the function being expressed. Computers
           | are discrete devices and a variable that "varies" across its
           | domain inherently implies that it's going to be computed more
           | than once. The sense Carmack is using, where it is not
           | recomputed and just amounts to a shorthand for a longer
           | expression, is a poor fit.
           | 
           | I do think this is sort of a wart in terminology, and the
           | upthread post is basically right that we've been using this
           | wrong for years.
           | 
           | If I ever decide to inflict a static language on the masses,
           | the declaration keywords will be "def" (to define a constant
           | expression) and "var" (to define a mutable/variable
           | quantity). Maybe there's value in distinguishing a "var"
           | declaration from a "mut" reference and so maybe those should
           | have separate syntaxes.
        
             | IshKebab wrote:
             | Well maybe global constants shouldn't be called
             | "variables", but I don't see how your definition excludes
             | _local_ immutable variables from being called  "variables".
             | E.g.                 fn sin(x: f64) -> f64 {         let x2
             | = x / PI;         ...
             | 
             | Is x2 not variable? It's value varies depending on how I
             | assign x.
             | 
             | Anyway this is kind of pointless arguing. We use the word
             | "variable". It's fine.
        
             | zahlman wrote:
             | > Those are synonyms, and this amounts to a retcon.
             | 
             | The point is that it varies _between_ calls to a function,
             | rather than _within_ a call. Consider, for example, a name
             | for a value which is a pure function (in the mathematical
             | sense) of the function 's (in the CS sense) inputs.
        
               | ajross wrote:
               | 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.
        
               | tialaramex wrote:
               | 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 assumes that 's the same thing the
               | result is confusing for programmers and so it usually
               | ends up requiring a language level fix.
               | 
               | e.g. "In C# 5, the loop variable of a foreach will be
               | logically inside the loop"
        
               | ajross wrote:
               | Seems like you're coming around to my side of the fence
               | that calling these clearly distinct constant expressions
               | "variables" is probably a mistake?
        
               | tialaramex wrote:
               | I don't think so? I've been clear that there are three
               | distinct kinds of thing here - constants, immutable
               | variables, and mutable variables.
               | 
               | In C the first needs us to step outside the language to
               | the macro pre-processor, the second needs the keyword
               | "const" and the third is the default
               | 
               | In Rust the first is a const, the second we can make with
               | let and the third we need let mut, as Carmack says
               | immutable should be the default.
        
               | ajross wrote:
               | There are surely more than three! References can support
               | mutation or not, "constants" may be runtime or compile
               | time.
               | 
               | The point is that the word "variable" inherently reflects
               | change. And choosing it (a-la your malapropism-that-we-
               | all-agree-not-to-notice "immutable variables") to mean
               | something that does (1) is confusing and (2) tends to
               | force us into worse choices[1][2] elsewhere.
               | 
               | A "variable" should reflect the idea of something that
               | can be assigned.
               | 
               | [1] In rust, the idea of something that can change looks
               | like a misspelled dog, and is pronounced so as to imply
               | that it can't speak!
               | 
               | [2] In C++, they threw English out the window and talk
               | about "lvalues" for this idea.
        
           | barisozmen wrote:
           | 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.
        
           | tredre3 wrote:
           | > In Rust we'd use `const` for this but in C you need to use
           | the C pre-processor language instead to make constants, which
           | is kinda wild.
           | 
           | I get that you're not very familiar with C? Because in C we'd
           | use const as well.                   const int x = 2;
           | x = 3; // error: assignment of read-only variable 'x'
        
             | superblas wrote:
             | Perhaps they're conflating how you can't use "const" as a
             | compile time constant (e.g., you can't declare the size of
             | an array with a "const" variable). If so, C23 solves this
             | by finally getting the constexpr keyword from c++
        
             | tialaramex wrote:
             | That's not a constant, that's an immutable variable which
             | is why your diagnostic said it was read-only.
             | const int x = 2;        int *p = &x;        *p = 3; // Now
             | x is 3
             | 
             | And since I paid for the place where I'm writing this with
             | cash earned writing C a decade or so ago, I think we can
             | rule out "unfamiliar with C" as a symptom.
        
         | garethrowlands wrote:
         | The term 'variable' is from mathematics. As others have said,
         | the values of variables do vary but they do not mutate.
        
           | astrobe_ wrote:
           | Yes, and math has the notion of "free variable" and "bound
           | variable" [1].
           | 
           | [1] https://en.wikipedia.org/wiki/Free_variables_and_bound_va
           | ria...
        
         | throwaway_7274 wrote:
         | Right, yeah, it's a funny piece of terminology! The sense in
         | which a 'variable' 'varies' isn't that its value changes in
         | time, but that its value is context-dependent. This is the same
         | sense of the word as used in math!
        
         | jayd16 wrote:
         | A common naming is value. You can call them immutable values
         | and mutable variables.
         | 
         | Another way to look at it is a variables are separate from
         | compile time constants whether you mutate them or not.
        
         | nayuki wrote:
         | > What is a "variable" if not something that varies?
         | 
         | If I define `function f(x) { ... }`, even if I don't reassign x
         | within the function, the function can get called with different
         | argument values. So from the function's perspective, x takes on
         | different values across different calls/invocations/instances.
        
         | didibus wrote:
         | 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.
        
         | Warwolt wrote:
         | It's a variable simply because it doesn't refer to a specific
         | object, but any object assigned to it as either function
         | argument or by result of a computation.
         | 
         | It's in fact us programmers who are the odd ones out compared
         | to how the word variable has been used by mathematics and
         | logicians for a long time
        
         | usrusr wrote:
         | Some languages like Kotlin have var and val introducing the
         | distinction between variables (that are expected to get
         | reassigned, to _vary_ over time, and values, which are just
         | that, a value that has been given a name. I like these small
         | improvements.
         | 
         | (unfortunately, Kotlin then goes on and introduces "val get()"
         | in interfaces, overloading the val term with the semantics of
         | "read only, but may very well change between reads, perhaps you
         | could even change it yourself through some channel other than
         | simple assignment which is a definite no")
        
         | hannasm wrote:
         | You could always interpret a variable from the perspective of
         | it's memory address. It is clearly variable in the sense that
         | it can and will change between allocations of that address,
         | however an immutable variable is intended to remain constant as
         | long as the current allocation of it remains.
        
         | layer8 wrote:
         | Variables are called variables because their values can vary
         | between one execution of the code and the next. This is no
         | different for immutable variables. A non-variable, aka a
         | constant, would be something that has the same value in all
         | executions.
         | 
         | Example:                 function circumference(radius)
         | return 2 * PI * radius
         | 
         | Here _PI_ is a constant, while _radius_ is a variable. This is
         | independent of whether _radius_ is immutable or not.
         | 
         | It doesn't have to be a function parameter. If you read
         | external input into a variable, or assign to it the result of
         | calling a non-pure function, or of calling even a pure function
         | but passing non-constant expressions as arguments to it, then
         | the resulting value will in general also vary between
         | executions of that code.
         | 
         | Note how the term "variable" is used for placeholders in
         | mathematical formulas, despite no mutability going on there.
         | Computer science adopted that term from math.
         | 
         | https://en.wikipedia.org/wiki/Variable_(mathematics)
        
         | ychen306 wrote:
         | I try to avoid this ambiguity by calling such variables
         | "values".
        
         | MetaWhirledPeas wrote:
         | > What is a "variable" if not something that varies?
         | 
         | Really it's a constant. But they are referenced like variables,
         | so people just get a little lazy (or indifferent) talking about
         | it.
        
       | wodenokoto wrote:
       | Mutability was by far the most difficult thing when learning
       | Python and mutating objects by iterating over its items do get
       | confusing, even as a senior.
       | 
       | When I was first learning I thought all methods would mutate. It
       | has a certain logic to it
        
       | storus wrote:
       | I had enormous fun optimizng C++ via self-modifying assembly to
       | squeeze the utmost performance of some critical algorithms, and
       | now this drive towards immutable everything feels like cutting my
       | hands and legs off and forcing me to do subpar engineering.
        
         | ryandrake wrote:
         | Same here, I grew up in a world where you had a handful of
         | registers and a bunch of memory locations, and those were your
         | variables.                               clc
         | lda value                     adc #1                     sta
         | value                     lda value+1                     adc
         | #0                     sta value+1              value
         | .byte 0,0
         | 
         | These constraints are pretty much built into my concept of
         | programming and it would take great effort to break out of it.
         | It feels nice doing:                   x = 20         x = x +
         | func(y)         x = x / func(z)
         | 
         | And it would feel weirdly wasteful to do:                   x =
         | 20         x1 = x + func(y)         x2 = x1 / func(z)
         | 
         | Like, I know that variables are no longer a scarce resource,
         | but my brain still wants to conserve them.
        
           | 1718627440 wrote:
           | To me this also seem to be wasteful, even if not for the
           | computer, but it wastes my amount of working state I can keep
           | in my head, which is very limited.
        
             | restalis wrote:
             | I was thinking about this too. When you read a program,
             | there is this information payload, which is the
             | metaphorical ball you have to keep your eyes on, and more
             | or less forget about the rest as soon as it isn't relevant
             | any more. In the functional paradigm it's like seeing the
             | juggle of a bunch of such balls instead (plus the
             | expectation to admire it), but that's just wasteful on
             | reader's attention.
        
         | no_wizard wrote:
         | Compiler optimizations make up for it, usually. Thats been my
         | experience at least.
         | 
         | An important thing to keep in mind is just how far compilers
         | have come over the least 40 years. Often, with immutable
         | languages, they can do extremely efficient compile only
         | optimizations because of said guaranteed immutability by
         | default.
         | 
         | Its not true in every case, of course, but I think for most
         | situations compilers do a more than good enough job with this.
        
       | warmwaffles wrote:
       | This shouldn't be a hard and fast rule for everything. Be treated
       | as guidelines and allow the programmer some wiggle room to reuse
       | variables in situations that make sense.
        
         | AnotherGoodName wrote:
         | Going too hard on mutation means you usually end up with larger
         | structures that are recreated completely. Those themselves are
         | then the point of mutation. This can be helpful if the larger
         | object needs a lot of validation and internal cross rules (eg.
         | You can't set 'A' if 'B' is true, you can validate that when
         | recreating the larger object whereas if 'A' was mutable on it's
         | own someone might set it and cause the issue much later which
         | will be a pain to track down).
         | 
         | Anyway the outcome of trying to avoid mutation means instead of
         | simply setting player.score you get something like player = new
         | Player(oldPlayerState, updates). This is of course slow as
         | hell. You're recreating the entire player object to update a
         | single variable. While it does technically only mutate a single
         | object rather than everything individually it's not really
         | beneficial in such a case.
         | 
         | Unless you have an object with a lot of internal rules across
         | each variable (the can't be 'A' if 'B' example above) it's
         | probably wrong to push the mutation up the stack like that. The
         | simple fact is a complex computer program will need to mutate
         | something at some point (it's literally not a turing machine if
         | it can't) so when avoiding mutation you're really just pushing
         | the mutation into a higher level data object. "Avoid mutations
         | of data that has dependencies" is probably the correct rule to
         | apply. Dependencies need to be bundled and this is why it makes
         | sense not to allow 'A' in the above example to be mutated
         | individually but instead force the programmer to update A and B
         | together.
        
       | acdbddh wrote:
       | In python its common to see code like this:                 df =
       | pd.concat(df,other_df)       df = df.select(...)       ...
       | 
       | My eyes hurts, when I see it. It makes me avoid python. I bet the
       | reason for this mutable code is a missing simple pipes syntax. I
       | love pipes. In R can do:                 df |>
       | rbind(other_df) |>          select(...)
       | 
       | It feels much better.
        
         | mr_luc wrote:
         | Elixir too (Explorer library; default backend is Pola.rs based)
         | 
         | - https://github.com/elixir-explorer/explorer -
         | https://hexdocs.pm/explorer/Explorer.html
        
         | fermisea wrote:
         | pandas has a .pipe operator which works exactly like this
        
         | cubefox wrote:
         | Wouldn't that be simply:                 df =
         | pd.concat(df,other_df).select(...)
        
         | leviathan wrote:
         | My eyes would hurt more if I had to look all day at the
         | vertical misalignment of that |> operator
        
           | kec wrote:
           | You could always switch to a better font like Fira Code which
           | has a ligature for this.
        
             | adregan wrote:
             | The gp's comment wasn't made regarding the look of the
             | operator in its ascii representation `|>` but about the
             | vertical misalignment.
             | 
             | Typically you align a pipeline like so:
             | df          |> rbind(other_df)          |> select(...)
             | 
             | But these topics are largely left to code formatters these
             | days.
        
         | zenlot wrote:
         | Nah, it doesn't. Although you can do similar in Elixir.
        
       | andsoitis wrote:
       | Is there another programming language that comes close to
       | Clojure's persistent data structures?
        
       | TheRoque wrote:
       | I also default to const in javascript. Somehow a "let" variable
       | feels so dirty, but I don't really know why. I guess at this
       | point my experience forged an instinct but I can't even put a
       | theory on it. But it's sometimes necessary and I use it of
       | course.
        
         | catlifeonmars wrote:
         | In JS, by using const, you are signalling to the reader that
         | they don't need to look out for a reassignment to understand
         | the code. If you use let, you are saying the opposite.
        
         | maleldil wrote:
         | JS's const doesn't go far enough since you can still mutate the
         | object via its methods. In C++, you can only call const methods
         | (which can't mutate the object) on const variables.
        
       | bitbasher wrote:
       | > ... making almost every variable const at initialization is
       | good practice. I wish it was the default, and mutable was a
       | keyword.
       | 
       | Rust mentioned!
        
         | willismichael wrote:
         | Here's a relevant comment on Rust:
         | https://x.com/ID_AA_Carmack/status/1094419108781789184
        
           | maleldil wrote:
           | That was 6 years ago. I'd like to see how that feeling
           | developed.
        
         | scuff3d wrote:
         | And Zig :)
        
           | alternatex wrote:
           | Plus F# and a whole family of FP languages.
        
       | shermantanktop wrote:
       | Const/final by default - preach it, brother, amen and hallelujah.
       | Mutability by default is the rule in the big languages and the
       | world would be a better place if it weren't.
        
       | textlapse wrote:
       | I wish C++ did some sane things like if I have a const member
       | variable, allow me to initialize it as I wish in my constructor -
       | it's a constructor for crying out loud.
       | 
       | Don't be silly and assume if I assign it multiple times in an if
       | condition it's mutable - it's constructing the object as we
       | speak, so it's still const!!!
       | 
       | C# gets this right among many other things (readonly vs const,
       | init properties, records to allow immutability by default).
       | 
       | And the funny thing is the X thread has lots of genuine comments
       | like 'yeah, just wrap a lambda to ensure const correctness' like
       | that's the okay option here? The language is bad to a point it
       | forces good sane people into seeing weird "clever" patterns all
       | the time in some sort of an highest ELO rating for the cleverest
       | evilest C++ "solution".
       | 
       | I was hoping Carbon was the hail mary for saving C++ from itself.
       | But alas, looks like it might be googlified and reorged to
       | oblivion?
       | 
       | Having said that, I still like C++ as a "constrained C++"
       | language (avoid clever stuff) as it's still pretty good and close
       | to metal.
        
       | QuadrupleA wrote:
       | Love Carmack, but hard disagree on this and a lot of similar
       | functional programming dogma. I find this type of thing very
       | helpful:                   classList = ['highlighted', 'primary']
       | if discount:             classList.append('on-sale')
       | classList = ' '.join(classList)
       | 
       | And not having to think about e.g. `const` vs `let` frees up
       | needless cognitive load, which is why I think python (rightly)
       | chose to not make it an option.
        
         | ColeShepherd wrote:
         | Some potential alternatives to consider:
         | 
         | 1.                   classList = ['highlighted', 'primary']
         | .concatif(discount, 'on-sale')             .join(' ')
         | 
         | 2.                   classList = ' '.join(['highlighted',
         | 'primary'] + (['on-sale'] if discount else []))
         | 
         | 3.                   mut classList = ['highlighted', 'primary']
         | if discount:             classList.append('on-sale')
         | classList = ' '.join(classList)              freeze classList
         | 
         | 4.                   def get_class_list(discount):
         | mut classList = ['highlighted', 'primary']             if
         | discount:                 classList.append('on-sale')
         | classList = ' '.join(classList)             return classList
         | classList = get_class_list(discount)
        
         | salutis wrote:
         | Fennel (Lisp):                   (table.concat [:highlighted
         | :primary (if discount :on-sale)] " ")
        
       | zahlman wrote:
       | > and it avoids problems where you move a block of code and it
       | silently uses a version of the variable that wasn't what it
       | originally had.
       | 
       | I find that keeping functions short also helps a ton with that.
       | 
       | No, shorter than that. Short enough that the only meaningful
       | place to "move a block of code" is into another function. Often,
       | by itself.
        
         | williamdclt wrote:
         | It helps with that, but it has other trade-offs: indirection
         | isn't free for readability.
        
           | throw10920 wrote:
           | I'll go further than that and say that indirection
           | significantly increases cognitive load and hurts readability.
        
             | zahlman wrote:
             | I have consistently found the opposite to be the case
             | across decades of programming experience, as regards the
             | extraction of helper functions. This is not "indirection"
             | in the same sense as with data structures. It is
             | _abstraction_ of currently-irrelevant detail.
             | 
             | Reading and understanding code is a process of answering
             | "what are the immediate steps of this task?", _without_
             | thinking about what those steps consist of or entail. It is
             | not a process of answering  "where is the variable
             | representing ..., and the code that manipulates this?",
             | especially since this makes assumptions that may prove
             | incorrect.
        
           | tengbretson wrote:
           | > indirection isn't free for readability
           | 
           | Yes, but also no. If its a mostly side-effect free function
           | with a good name and well defined input/output types its
           | basically free.
        
       | dinkblam wrote:
       | // !!!: SWIFTY VAR & LET SUPPORT
       | 
       | #define var __auto_type
       | 
       | #define let const __auto_type
        
       | thefaux wrote:
       | Even better is to use tail calls instead of loops and eliminate
       | mutable variables entirely.
        
       | mtlmtlmtlmtl wrote:
       | It's fascinating how even when Carmack says something rather
       | obvious and unoriginal, that many people have said before,
       | sometimes decades ago, it still spawns a 400+ comment thread on
       | HN. I really don't get it, it's almost like a cult of personality
       | at this point.
        
         | kins wrote:
         | It always was.
        
       | GTP wrote:
       | In other words, he wishes he used Rust.
        
       | groby_b wrote:
       | It is really amazing in how many ways C/C++ made the wrong
       | default/implicit choices, in retrospect.
       | 
       | Hindsight's 20/20, of course. But still.
        
       | rezonant wrote:
       | https://xcancel.com/id_aa_carmack/status/1983593511703474196
        
         | seattle_spring wrote:
         | CMV: HN should just automatically replace x links with xcancel
        
       | avadodin wrote:
       | We're all already doing this as the compiler turns everything
       | into SSA form, silly goose.
        
       | WalterBright wrote:
       | I'm curious where this fits in with single assignment semantics:
       | int x = 3;         x = 4; // error!         int* p = &x;
       | *p = 4; // is that an error?
        
         | taf2 wrote:
         | At least with clang it's a warning:                   f.c:4:8:
         | warning: initializing 'int *' with an expression of type 'const
         | int     *' discards qualifiers [-Wincompatible-pointer-types-
         | discards-qualifiers]         4 |   int* p = &x;           |
         | ^   ~~         1 warning generated.
        
         | wicharek wrote:
         | Yes, because line 3 would implicitly be: const int * p
        
       | waffletower wrote:
       | Would be beneficial if Rich Hickey's opinion and experience
       | regarding mutable state was given more weight than John
       | Carmack's.
        
       | carabiner wrote:
       | Does anyone have any real naming conventions, patterns for doing
       | this in ds programming in notebooks? I've got a bad habit of
       | doing:                 df = pd.read_excel()       df =
       | df.drop_duplicates.blahblah_other_chained_functions()       [20
       | cells later]       df = df.even_more_fns()
        
       | armaoin wrote:
       | Totally agree. const auto for me is the default.
        
       | markstos wrote:
       | "State is the enemy".
       | 
       | Every new state is an additional condition to check. A mutated
       | variable implies an additional state to test.
       | 
       | One boolean means two states to test. Four booleans that interact
       | is 2^4 (16) states to test.
        
         | baruchel wrote:
         | Which is why functional programmers believe in the separation
         | of Church and state.
         | https://wiki.c2.com/?SeparationOfChurchAndState
        
       | AtNightWeCode wrote:
       | I care less and less about things like this. At some point you
       | will write code in some lang that have fancy keywords and stuff
       | gets mutated anyway. Also, what people tends to do if stuff is
       | immutable is that they hide mutation by doing deep copies with
       | changes.
        
       ___________________________________________________________________
       (page generated 2025-10-31 23:02 UTC)