1. 27
  1.  

  2. 16

    Suggestion: I think this would be a much stronger article if you include examples. Don’t just tell me why immutability is better, show me why immutability is better!

    1. 3

      In this article there are some basic examples which helped me transitioning to functional (and reactive) programming: https://blog.danlew.net/2017/07/27/an-introduction-to-functional-reactive-programming/

      1. 3

        That is indeed a good suggestion! I wanted it to be really short (and maybe a bit.. religious?), but I can definitely agree that some examples would make it stronger. Thanks for the feedback!

        1. 3

          On the “Reasoning” front, consider this framing: The more parameters a function takes, the harder it is to reason about. And every mutable object that is in scope and reachable from that function’s code is, in effect, a parameter it takes. (And that’s before you even get to the concurrency issues.)

          (ETA: I suppose that’s still “tell”, but it’s vivid in a certain way.)

          1. 2

            You’re doing great work - and yes I’d love to see real examples too please. I’m already an FP acolyte, but others would surely benefit.

        2. 9

          Here’s a thought: immutability is to mutability as static typing is to dynamic typing. You can punch a hole in an immutable language to provide (constrained) mutability, but within a mutable language you can only emulate immutability at run-time. This parallel becomes more obvious if you rename immutability to “static state” and mutability to “dynamic state”.

          Therefore, even by this relationship of subsumption alone (i.e. considering none of the practical benefits), immutability is a superior default.

          1. 16

            I agree with the general thesis that data should be immutable by default. This is once of the things that I greatly appreciated in Haskell, coming imperative languages. However, I now like the approach of Rust’s borrows checker more. It permits easy mutability when necessary, often leading to more straightforward solutions and avoiding copying. But it resolves many of the problems with mutability in other languages. From the article:

            If I pass my data to a function to have some computation performed, I expect that function to play nice and don’t change my data in the process. But if my data structures are mutable, how can I be sure they don’t?

            Make functions take immutable references, e.g.:

            fn frob(foo: &Foo) -> Bar
            

            However, if I actually want a function to modify a data structure, I do not have to jump through hoops:

            fn frobber(foo: &mut Foo)
            

            What would happen if you had two threads operating on the same, shared data structure – one reading from it while the other mutates it? Best case scenario, your program blows up and execution stops.

            This cannot happen in Rust, because the type system prevents mutable references to be shared between threads. The only way to permit mutation from different threads is to synchronize access. (This is all enforced through the Send and Sync traits.)

            1. 8

              Since I code in Rust, the whole “make data structures immutable” stance has suddenly been made empty to me.

              Trying to make data structures immutable was a wrong take at the real problem.

              1. 5

                IMHO total immutability makes sense in simpler, higher level and garbage-collected programming languages like Erlang or Elixir. Of course, immutability makes people life harder very often but it’s important to realize that a safe and concurrent immutable programming language don’t need a borrow checker and thus can be easier to learn.

                Also, this doesn’t work very well for some applications. In my experience immutability is really nice for the average web app (I mean a thing that queries databases and outputs HTML), but I would probably not choose Erlang for, let’s say, a video game.

              2. 6

                I definitely have had a similar experience, but I feel like the key observation is that that the problem lies in limiting shared, mutable data. Most languages like Haskell, OCaml, Erlang etc. deals with it by limiting mutation of data. (Where did that idea start anyway, was it with original ML?) Rust does this too, but really deals with the problem more by limiting sharing of data. It’s a new and extremely powerful approach.

                That said, I feel like the old approach is still valid and useful at times. I spent a few weeks playing around with an Erlang/Akka-like actor system in Rust called axiom, and combining it with the im immutable data structure crate worked really, really well.

                1. 4

                  I can highly recommend Bodil Stokkes Talk at RustFest Paris: https://www.youtube.com/watch?v=Gfe-JKn7G0I

                  It goes into many reasons why immutable datastructures with structural sharing are useful beyond safety.

                  1. 4

                    I saw that talk live, it is great. That’s what inspired me to use im in the first place!

                2. 3

                  Well, Rust is in a league of its own ;) I haven’t found the time to start learning Rust yet, but making concurrent programming safe with types sounds really interesting. It’s on my list of things to dive into, I just need to find time. Thanks for the input!

                  I won’t start a big argue around immutable and mutable references, but the whole idea is that I want things to be immutable by default. If things are mutable by default, nothing more than conventions stops you from mutating, and that will eventually lead to errors. If things are immutable by default, you have to opt out of immutability, making it an explicit action. I am not necessarily against this, because as you say some situations demands a bit more pragmatism than pure immutability can provide.

                  1. 9

                    Just to sum it up, the stance of Rust is not that “mutability is evil” but “shared mutable access is evil”.

                    1. 6

                      I’m like a broken record on this board it would seem, but check out Idris. Way more powerful than even Haskell: your concurrent programms can to conform to arbitrary protocols that you write in the same language. Because types are first-class, they can be computed on the fly conditionally and used to statically confirm that several systems are coordinated in some complex way.

                      1. 3

                        Rust is absolutely immutable by default. If you want something to be mutable you have to mark it as such. However once you have marked it. Rust will then go a step further and ensure that no two threads can attempt to modify the mutable item at the same time. It also allows you to scope mutability to single functions or blocks which limits the number of code locations that something can be mutable.

                    2. 6

                      Immutable values are a good idea, but have nothing to do with functional programming. Are we going to get an entire month’s worth of such posts?

                      C has immutable integral types. Python has immutable tuples. Haskell has mutable references in IO, ST, and STM. Scheme has (set!) forms. That should be sufficient to totally discount the connection that you are imagining.

                      If I pass my data to a function to have some computation performed, I expect that function to play nice and don’t change my data in the process. But if my data structures are mutable, how can I be sure they don’t? Actually, there is nothing more than conventions to stop that from happening. If my data structures are immutable, change is prohibited by the compiler itself.

                      This is a simple way of looking at things. In particular, two important nuances happen daily:

                      • We don’t really need the compiler to prohibit changes, just the runtime. C-style const annotations are known to the compiler, sure, but in Java or Python, objects are immutable because of their class definitions, and the runtime enforces finality, constness, etc.
                      • Haskell, Rust, and many many other languages with strong safety properties carry unsafe escape hatches which can break those properties arbitrarily and with little-to-no warning.

                      The easiest though, at least in my opinion, is simply making your data structures immutable. When shared resources can’t change, threads can’t cause issues for each other as easily.

                      Threads are not necessary for concurrency issues, and it is possible for a single-threaded program to still suffer concurrency bugs. I’d like to introduce you to Concurrency Among Strangers. The problem is actually that code can reëntrantly call itself; more generally, this is plan interference. It can happen even when values are immutable, as long as it’s possible for a procedure to call itself while it’s calling itself.

                      Now, this might be where you start thinking “creating new copies all the time.. that sure sounds expensive.”. And I wouldn’t blame you for thinking like that, because naively implemented that might actually be true. But let me assure you that correctly implemented, this pattern is no more resource intensive than modifying in place.

                      Could you show us how to ensure that we’ve “correctly implemented” the technique? Perhaps with an example? What would be really cool is a constructive proof.

                      1. 4

                        Let’s start with your last point first. Showing how one can correctly implement efficient immutable data structures is way beyond the scope of this tiny blog post. If you are curious, two keywords are persistent data structures and structural sharing. This is what Clojure uses natively, and libraries like Immutable.js implements them in JavaScript as well. There are quite a few blog posts to read about this :)

                        Immutable values are a good idea, but have nothing to do with functional programming. Are we going to get an entire month’s worth of such posts?

                        C has immutable integral types. Python has immutable tuples. Haskell has mutable references in IO, ST, and STM. Scheme has (set!) forms. That should be sufficient to totally discount the connection that you are imagining.

                        Yes, immutability exists in non-functional languages, and yes, mutability exists in functional languages. But that does not imply that non-functional languages promotes immutability, or that functional languages promotes mutability. Quite the opposite, in fact. You can argue the same way about monads, higher order functions, recursion and laziness as well, but this way of arguing is quite counter productive. Immutability is a really important aspect of functional languages, and that is the reason it exists on this blog.

                        Ohh, and as a side note, Wikipedia mentions immutability in the first sentence in its article on functional programming (https://en.wikipedia.org/wiki/Functional_programming#Strict_versus_non-strict_evaluation).

                        This is a simple way of looking at things. In particular, two important nuances happen daily:

                        It might be a simple way of looking at things, but I really like having as much safety in the compiler as possible. The compiler outsmarts me by miles when it comes to turning higher level code into lower level code, so if it wants to help me catch bugs then I’m all in.

                        … and it is possible for a single-threaded program to still suffer concurrency bugs.

                        This is definitely something I haven’t given much thought before. Sounds like I have some late night reading to do – thanks!

                      2. 2

                        Tangentially related, but Ive recently been learning Dart, and I noticed that they use “Async by default”:

                        • readAsString vs readAsStringSync
                        • copy vs copySync

                        https://api.dartlang.org/stable/dart-io/File-class.html

                        1. 1
                        2. 2

                          Now, this might be where you start thinking “creating new copies all the time.. that sure sounds expensive.”. And I wouldn’t blame you for thinking like that, because naively implemented that might actually be true. But let me assure you that correctly implemented, this pattern is no more resource intensive than modifying in place.

                          I was a little bummed at the lack of examples, but this massive handwave at the end really got me.

                          There are plenty of examples, say math or graphics routines, where immutable techniques are basically impractial in most languages. Even for boring line of business stuff, languages that have libraries that emulate immutable behavior (JS) can still have trouble being performant and dealing with GC issues.

                          1. 5

                            For a good example of (some) fast immutable data structures, I will echo skade’s recommendation of Bodil Stokke’s talk: https://www.youtube.com/watch?v=Gfe-JKn7G0I . “No more expensive” is not a great statement, I’d call it “usually nearly as fast, sometimes far faster, and usually more correct”.

                            That said, you’re right, there’s always going to be situations where it just isn’t practical.

                            1. 2

                              I don’t know about “no more resource intensive” but OCaml’s GC (and many others, I’m sure) is built around the idea that most objects are small (so copy-and-update isn’t expensive), and short-lived so they can be dropped during the constant-time minor heap collection. The end result is fairly fast. I would also think any mutable-centric GC would have more trouble with GC on immutable objects than an immutable-centric GC.

                              Also, of course, any time an immutable object is updated, it can be safely mutated in-place if there’s only one reference to it. I don’t know how often this is done in practice though, since it could require two versions of every update procedure.

                              1. 2

                                Definitely. There is a penalty to pay, both in performance and also (IMO) readability in some cases. Pretending otherwise robs programmers of the knowledge required to sensibly choose when and how to accept these trade-offs.

                              2. 2

                                “Immutable by default” is a special case of “safe by default”.

                                Why safe by default?

                                Jussi Pakkanen’s law of language design:

                                If there are two ways to do something, one easy and one correct, programmers will always choose the easy.

                                1. 1

                                  I mostly agree. Things should be immutable “at the boundaries” - once shared from where they’re created. In my experience, code to construct a thing that may take multiple shapes, or can be of multiple types, is a lot easier to read and write (if not reason about mechanically) when written against mutable local state.