▲ | henning 3 days ago | |
So behind the scenes, every one of those statements will make a whole new user object with a whole new address object so that it remains immutable? And whether that will actually have any real-world performance impact is I guess entirely situational. Still, what happens if you do that with a big object graph? Also, the original strong need for immutable data in the first place is safety under concurrency and parallelism? | ||
▲ | sriram_malhar 3 days ago | parent | next [-] | |
Yes, behind the scenes every one of those statements will make a shallow copy of the object. But it isn't just that object necessarily. For example, if you modify a tree node, then not only does that node needs cloning, its parent does too (since the modified parent needs to point to the new node), and so on until the root, which results in h = O(log(n)) new objects to create an entirely new tree. (h is the height of the tree). What you get out if it is (a) safety, (b) understandability, which are wonderful properties to have as long as the end result is performing adequately. Implementing concurrent tree or graph traversals under conventional mutation is painful; the Java collection libraries simply throw a ConcurrentModificationException. The equivalent code for readonly traversals of immutable data structures is simplicity itself. You also get versioning and undo's for free. | ||
▲ | Nullabillity 3 days ago | parent | prev | next [-] | |
> Still, what happens if you do that with a big object graph? The only thing that really matters here is how deep the graph is. Any unchanged object can just be reused as-is. | ||
▲ | kelnos 3 days ago | parent | prev | next [-] | |
This is in general how "mutations" are supposed to be done in a language like Scala (and is not unique to this library). Yes, Scala does have a set of mutable collections, but the immutable collections are heavily optimized to make creating a "new" collection with a mutation much cheaper than having to copy the entire collection. Of course, copying a case class in order to change a field likely does require a full copy of the object, though since this is the JVM, things like strings can be shared between them. Ultimately this pattern is... fine. Most uses don't end up caring about the extra overhead vs. that of direct mutation. I don't recall if the Scala compiler does this, but another optimization that can be used is to actually mutate an immutable object when the compiler knows the original copy isn't used anywhere else after the mutation. > Also, the original strong need for immutable data in the first place is safety under concurrency and parallelism? That's one of the uses, but multiple ownership in general is another, without the presence of concurrency. On top of that, there's the general belief (which I subscribe to) that mutation introduces higher cognitive load on someone understanding the code. Immutable data is much easier to reason about. | ||
▲ | lmm 3 days ago | parent | prev | next [-] | |
> So behind the scenes, every one of those statements will make a whole new user object with a whole new address object so that it remains immutable? Not a "whole new" one since it will use shared references to the parts that didn't change (which is valid since they're immutable). And in principle the VM could even recognise that the new object is replacing the old one so it can be edited in place. > Still, what happens if you do that with a big object graph? I've literally never seen it cause a real-world performance problem, even if it theoretically could. > Also, the original strong need for immutable data in the first place is safety under concurrency and parallelism? Partly that, but honestly mostly development sanity and maintainability. You can iterate a lot faster on immutable-first codebases, because it takes much less test coverage etc. to have the same level of confidence in your code. | ||
▲ | valenterry 3 days ago | parent | prev | next [-] | |
Your question is a bit like someone asking "so what does the garbage collector actually do? Does it X or Y? What impact does it have?" And the answer is: no need to care about it. Unless you need to really optimize for high performance (not necessary in 99% of the cases, otherwise you'd use a different language from the beginning anyways). > Also, the original strong need for immutable data in the first place is safety under concurrency and parallelism? One of the reasons is that you really can just completely stop thinking about it. Just like you can stop thinking of (de)allocations. Except for some edge-cases when performance matters a lot. | ||
▲ | threeseed 3 days ago | parent | prev [-] | |
> actually have any real-world performance impact There are many techniques like this within Scala that would never be feasible if it wasn't for the fact that the JVM is ridiculously fast. You could write the worst code imaginable and in many cases would still have better performance than Python, Javascript etc. |