Hacker News new | ask | show | jobs
by amw-zero 1822 days ago
> 1. Use immutability by default, even in languages that make that harder than it should be

What I've come to realize is that whenever people talk about wanting immutability, they really want value semantics. Value semantics can be achieved without immutability - the best example is Swift's structs. They can be both mutable or immutable, but they can never be shared, i.e. there can never be more than one reference to a single struct value. This means that modifying the value has no effect on other references, because there can be no other references.

This makes whether or not the value is immutable irrelevant, or at least not necessary. You get the same benefits - you can reason about your code locally and not worry about your changes modifying unknown parts of the program. Local reasoning is the end goal, and value semantics is the mechanism of getting to that goal. Immutability is only one way of getting value semantics.

1 comments

Can you explain in more detail? I agree that local reasoning is the end goal, but as soon as you change things (i.e. mutate) then it doesn't work anymore. Or in other words: if modifying a value has no effect on anything else, then why would you modify it? I don't get it - maybe a code example would help.
An example might be a function that takes a vector3, adds 5 to the x value, then returns the vector's length. You could model that as "make a second vector v_2 {x = v.x + 5, y = v.y, z = v.z}; return v_2.length", but if you have struct semantics, you can just do "v.x += 5; return v.length", and be confident that you're not modifying the vector that the caller has.
I see - but then you would still not have guaranteed local reasoning within the part of the code that modifies the vector.

Is that really worth it?

Also, creating the second vector should really look like "v2 = v.copy(x = x + 5); return v2.length". Or even just "return v.copy(x = x + 5).length".

You showed why it is worth it - it also avoids copying. Mutation is more efficient in every way, except cognitive overhead. So by preventing _sharing_ of mutable values, you get the best of both worlds.

This is the same model that Rust takes too. It doesn't eliminate mutability, only controls it. The argument that "performance doesn't matter anymore because computers are so fast" is a bad one. Efficiency is efficiency, and immutability will always be less efficient.

It's certainly fair to balance/decide between performance and local reasoning. But one should be clear then that they give up one for the other and not claim that both is possible. Because from my understanding from what you said, local reasoning isn't possible anymore, even though reasoning is still easier compared to when you don't have struct semantics. But that's still two different things.
You still have local reasoning with struct semantics. You are not giving that up in exchange for performance, you get both.

  func newStructValue(s: Struct) -> Struct {
    s.value = 5

    return s
  }
The reasoning for this block of code is totally local to the function body. Because the only reference to `s` is in the body of the function - it cannot be more local than that.