let foo = MakeMeAnObject();
let bar = MakeSomethingElse(foo);
DoSomethingForSideEffects(foo, JoinThings(bar, baz));
return foo;
With immutable objects, you can be certain that neither foo nor bar were modified by any of this code. So, when e.g. debugging a problem with that DoSomethingForSideEffects() call, you don't have to worry about values of foo and bar having been changed somewhere, by someone, between their introduction and the point you're debugging.Neither of them can be modified by it in the future - e.g. someone else can't change MakeSomethingElse() to also modify its inputs, thereby accidentally breaking your code that's using this function.
Another way of looking at it: a lot of problems with reasoning about code, or parallelism, can be drastically simplified by assuming the data being passed around is only copied, and not modified in-place. "Immutable objects" as a language feature is just making this assumption the default, and enforcing it at the language/compiler level.
In terms of use, it isn't that much more inconvenient over mutable objects. You can always do something like:
foo = DoSomeTransformation(foo);
It's just that DoSomeTransformation() is not modifying the object, but instead returning a new instance of the same objects, with relevant fields having different values. The obvious drawback here is, for large data structures, there will be lot of copying involved - but that's where the languages with immutable objects are usually "cheating", e.g. by using sophisticated data structures masquerading as simple ones, as to only ever copy things that have actually changed (i.e. "distinct" objects end up sharing a lot of their data under the hood).I can't think of anything where I'd want that. Don't you end up needing infinite amounts of memory? Isn't it absolutely slow as balls copying all that stuff around?
Is it as fast mutable everything? No, some copy must happen, but is a mutable-standard languages as fast as raw hand tuned asm? Also (probably) no, but the trade offs are worth it. It likely matters a lot less than you think unless you're writing actually performance critical tight loop code vs just thinking about performance.
This way:
- passing an object around can always be by reference, since no one can change it
- depending on structures used, for changed bits you don't need to copy the entire structure, but simply shift pointers to old data or new data
- garbage collection can become trivial, and granular, since you know exactly what's new and old, and shifting data around becomes just moving pointers around (again, depends on implementation and optimisations)
There are downsides if you are not careful, of course:
- creating a bunch of new data that references old data will run out of memory, but this doesn't happen as often as you would think.
- sending data between processes/threads may result in copying that data (depends on implementation)
However, the upside is quite good: your data never changes under you. That is a call to func(data) doesn't sneakily change data. And all data becomes trivially thread-safe without mutexes or race conditions.
Why would I want to have a thing in memory, copy it to more memory, modify the copy, then free up the original memory every time? That just seems like a waste.
You don't do that, runtime does that for you.
The main benefit is better assumptions about code you write. The prime example is how different languages handle object construction and modification:
var date = new Date(2022, 11, 21)
var other = date.addDays(10)
The question is: what do `date` and `other` contain?Depending on the language the answer may surprise you. Some languages modify `date`. Others don't. And you never know which of the methods exposed on an object modify it unless you read documentation. Neither the compiler nor the type system can help you.
However, if objects are immutable, you are guaranteed that the original object, `date` is never modified. And if you need to use it again, you know it contains the same data without it suddenly changing.
This gives rise to another important property: you can send this to another thread without mutexes or without creating weird synchronization points like Java's AtomicInteger. Since the object cannot be mutated, threads don't have to fight for exclusive access to read it.
Var object=object() If (isValid(object)) // Do something
In which the isValid function modified the object in unexpected way and caused the issue. With mutability, I loose confidence in the code and literally have to read implementation of everything the object touches to be sure i understand what's going on. Much more relevant in bigger projects.
In theory the functional approach is more stable and predictable. But whether this really causes a lot of bugs in practice is another question.
for example (pseudocode)
var newUser = Person(name: "some guy", age: 0)
// newUser is replaced with a new object that is
// initialized with field.name and previous age (0)
// the old object is discarded
newUser.name = field.name
// object is passed as copy-on-write, assuming a 1 sec delay
// it will print the objects values at this point in time
// (age: 0) even if altered later
async(after:1) { print(newUser.age) } // prints 0
// age was changed to 32 a nanosecond later,
// but now a new object again is initialized
// instead of mutated
newUser.age = 32
print(newUser.age) // prints 32
----------
output:
32
0
[0] https://en.wikipedia.org/wiki/Value_semantics[1] https://stackoverflow.com/questions/27084007/what-is-value-a...
would appreciate a correction!
I like being able to trust that something is what I think it is because it cannot be something else. Meaning: if I know that something can’t change, I don’t have to check for eventual accidental change.
What's the use case of all this copying and shunting stuff around and consuming massive amounts of memory?
What kind of programs would you write that would benefit from this?
Wrong. For instance, state can be stored in locals. Two threads accessing the same structure, one reading and one writing, the reader loads some state into locals and the writer then invalidates that state in the middle of the reader's computation, and the reader proceeds to computing a result mixing old state and new state thus leading to inconsistency. This ABA problem is well known and it just can't happen if the structure is immutable. And this doesn't even go into cache coherency issues.
I frankly don't think you appreciate the number of hazards just making data structures immutable actually addresses, and given you don't seem aware of the hazards implicit to mutable data structures, I suppose that's not surprising.
> What kind of programs would you write that would benefit from this
Pretty much every single program that accesses a relational database benefits from this. There are a few of those around n in case you didn't know. Perhaps you've heard of multiversion concurrency control?
But a more common case is how I get my records from my db. Only the ORM can create those DB record for me ( because I set it up that way )
When I return a response; I compose another immutable response object.
And only my service layer can don that. ( it won’t compile elsewhere)
Don't you use scope? I don't think I use more than 1 or 2 global variable per system.
This is "safer" for any code still operating on the original object, as it will not be changed unexpectedly.
extern result fubar(struct foo *);
extern result snafu(struct foo const *);
Just by looking at the function prototype, I know that calling snafu() won't change the struct foo I pass it. I don't know what fubar() will do to it. Maybe it won't change, maybe it will. I'd have to check the implementation of fubar() to find out.