DEV Community

John Peters
John Peters

Posted on • Updated on

Immutibilty

In JavaScript, assignment of objects versus values must be understood. Did you know that all object assignments are by reference?

By Reference vs. By Value

When 'by reference' is fully understood, mistakes, never happen.

If 'by reference' is not fully understood; then mistakes always happen.


//a javascript object with name of john
let john = {firstname:'John', money:100};
// the first sign of trouble 
let jason = john
// Set first name to object name
jason.firstname = 'Jason';
// Nice now I have a Jason object with first name of Jason
Enter fullscreen mode Exit fullscreen mode

But Jason never will be John in real life, ever!

// why do this?
let jason = john;
// jason is only pointing to john!
Enter fullscreen mode Exit fullscreen mode

This bug, caused by the developer; will have catastrophic consequences.

  • Jason never deposited any money but has $100.00 (nice for Jason)
  • John's first name is now Jason.
  • The bank is totally confused

Cascading the Bug


// Jason deposits another $100.00
jason.money = jason.money += 100;  
console.log(john.money) // prints out 200
// What? john doesn't have $200.00 either

Enter fullscreen mode Exit fullscreen mode
  • john now has $200.00 but didn't put in extra $100.00
  • john benefits from this bug as long as he doesn't mind his new first name!
  • Bank is really mad now, the auditors have called in investigators.
  • They find it was a developer issue, whose performance review is now in the tubes for this year.

As we can see, Mutation is bad! Right?

Nope, the bad was in misunderstanding of how JavaScript objects work. Again, the root cause of the bug was a developer error!

A while back, a question about immutibilty was posted.

The best reason given was change detection. Either the pointers to the instances are the same or they are not the same. This leads to near instantaneous change detection for any two objects!

Instant change detection is wonderful; however, it alone does not justify mandatory adoption of the immutability pattern.

Today we are seeing developers buy into this concept. They are for example, creating Components where immutability is enforced.

There is a newer grid component in Angular that enforces immutability. Grids show rows, with each row containing multiple properties, which cannot be mutated!

Immutability means no property in any row of a grid can be changed! Instead, a new object must to be created with the change. This forces the view to show either a dialog to change it, or some other magical overlay thingy. Programmers must learn new ways to simply change a property value.

You mean I can't just change a bank deposit field if it's shown in a grid and then save it? Yes that's exactly the case with immutability.

With today's super cheap Memory and ultra fast CPUS with multiple cores, making new objects is not really an issue. However forcing developers to adopt a pattern where it's really not needed doesn't make sense.

Summary

Immutability is not mandatory ever! In real life, excepting for an alias, there's no reason to make one object the same reference as the other.

Changing a property in an object to reflect reality has been going on for 50+ years. If our code mutates objects correctly, there's no worries. Just update the property and save it.

Mandatory immutability is just a way to save unenlightened programmers from themselves. 👌

Top comments (13)

Collapse
 
miketalbot profile image
Mike Talbot ⭐

Absolutely. Also, even with modern computers no game programmer worth their salt is using immutability - no matter the language - the allocation of memory and manual or automatic management of freeing it are a cost you can't afford when striving to push out the most possible performance in 16.67ms. So game programmers have to learn to not make the mistakes immutability protects from. Not that this is right for everyone, it depends on the objective.

I also get that functional programming requires it, and I do like functional programming, but there are limitations with every approach. Observer pattern seems to sit uncomfortably with Functional programming.

Collapse
 
_hs_ profile image
HS

Good point for people that hate OOP hehe.
I use immutability regardles of the languge because you can write your own immutable data structue and it's mainly protection from DB tools like Entity Framework wich for given object to save tracks it by default and saves everything you touch after that. So if it's a service or other class it doesn't contain anything that I need to maintain so i leave it mutuable for framworks to do injections and so on. And it saves a lot of time and performance

Let's say you do ef.somerepo.update(obj) and if after that you modify obj.name or something and try to trigger ef.save() totally unrelated to previous action it modifies the object. Because anything that touches EF, if not specified, will be tracked by it.

So even if you think about it yourself some tools/people don't and this is why API developers now strive for immutable. However in many cases for complex data structure it's quite slow to do this.that.list.eachItemInIt.listInThoseItems.push... so performance in that case is on immutablity side where you just copy all of items step by step and use a bit more memory for short period in order to transform something

Collapse
 
jwp profile image
John Peters • Edited

Agreed, we've solved this issue by not returning the EF classes, rather we convert to models and return them instead of the EF class. Inbound requests use the model as well. All of our EF work is fully contained within each endpoint method. We do not maintain state on our endpoint 'sends and receives' with respect to EF, we allow the object mode mutation to happen in client of which when it is "posted" or "put" back we allow the action to define the meaning.

Posted = new, Put = update. After all if the front-end is doing validation who is the back-end to argue?

Thread Thread
 
_hs_ profile image
HS

Exactly what I did for former client. I made dtos, domain models, and ef models. Made inernal classes that extend domains with transformations only in specific packages like controllers are in same DLL as internals that have toDto and toDomain or reposistory dll for ef. Every developer hated me on the project since it was not possible to go from DTO to EF model in1 step or save DTO directly. You had 2 transformations each time you want to save or return.

Thread Thread
 
jwp profile image
John Peters

Did you ever get into CQRS? Is CQRS similar to this concept?

Thread Thread
 
_hs_ profile image
HS

Not actually, I did started something like CQRS but not actually following that particular pattern mainly because I didn't care enough to learn it in details. I just made some DTOs that are basically commands but I still keep same models for update and add mainly. Just some times I have different Add nad Update models (commands). As for query I mainly have those as models when I take more than 3 arguments on an endpoint which happened from time to time but I didn't care enough to transform them into special cases. I just used them in like "common" package or so

Thread Thread
 
jwp profile image
John Peters

Same exact feeling I had about it all those years ago when Deno Espisito ran articles on it. That's why I was asking you to see if I should look into it again. Tx!

Collapse
 
jwp profile image
John Peters • Edited

Interesting point on Observables. They are just fancy event constructs. With the DOM only using the event event model, there really is no logical reason for anyone to dislike Observables.

Collapse
 
craigmc08 profile image
Craig McIlwrath

I'm not sure I follow your argument. You first claim that people use immutability when they don't understand the complexities mutability brings. Then, you state that immutability is more complicated?

Immutability isn't a "fundamental misunderstanding." It's a pattern that trades performance for an easier to understand program. There are times for immutable data structures and times for mutable data structures. I strongly disagree with "It's more simple to just change the values, and to understand what is being done." Let's look at an example:

function findRoots(a, b, c) {
  let d = b * b;
  d -= 4 * a * c;
  if (d < 0) return [];
  d = Math.sqrt(d);

  let denom = 2 * a;

  let root1 = -b + d;
  root1 /= denom;
  if (d == 0) return [root1];

  let root2 = -b - d;
  root2 /= denom;
  return [root1, root2];
}

Your goal is to figure out how this function works. You read top-to-bottom, understanding how each variable is calculated. You get to root1 and you realize you forgot what d was. You go back and check how d was defined. Now you have to re-interpret every statement that modified d. In more complex code, you may have to juggle the definition of several mutable variables, possibly with functions that modify their state and you have to read those functions definitions. This isn't to say it's impossible to write readable code with mutable data, but it requires much more care to write and a larger mental model for the reader.

Additionally, weakly-typed languages have no correspondence with immutability. Many functional languages are very strongly-typed (I'd consider the type system of Haskell more powerful than Java, for example) and are designed around immutability.

Overall, this article emanates a vibe of "Real programmers naturally understand complicated mutable state," although I doubt that that was intended.

Collapse
 
jwp profile image
John Peters • Edited

The example shows poor programming technique.


// This is ambiguous, lazy, and the worst form of mutation.
// Its a terrible way to program in any language!
// Its what was done in "c" 40 years ago
// 93 characters

let d = b * b;
d -= 4 * a * c;
if (d < 0) return [];
d = Math.sqrt(d);

// A better way to program.
// Totally non-ambiguous, non lazy, self commenting. 
// No concern for mutability here.
// 193 characters

let taxmultiplier = taxbase*taxbase;
let exciseTax = 4;
let figuretax = taxmultiplier -(exciseTax*stateTax*countytax);
if(figuretax<0) return [];
let taxRoot = Math.sqrt(taxMultipier);

// even better, we have a tax rule a object which, for safety must be immutable; 
// unless the new taxable rates arrive of which we handle separately somewhere
// 138 characters

let a = {taxbase:.04, taxmultipler:'*2', stateTax:.1, countyTax:.05} 
return a.taxmultiplier -(a.exciseTax*a.stateTax*a.countytax);


// This allows us to pass in an object and return a number based on object content.
// 66 characters

function calcTax(a){
 return a.taxmultiplier -(a.exciseTax*a.stateTax*a.countytax);
}

// implementing a decorator
// 72 chars

function calcFloodTax(a){
 let floodtax = .005;
 let tax = calcTax(a);
 return tax * floodtax;
}

// a decorator with injected floodtax
// Total characters 32

function calcFloodTax(a, floodtax){
  return calcTax(a) * floodtax;
}

If a programmer can type 200 words per minute or 4 lines of code. From a time perspective it's just better to be a better programmer.

My main point is if the programming is done correctly we should be able to mutate any time we want. It's just way easier to set a field to a new value than to do the immutable tricks.

Collapse
 
craigmc08 profile image
Craig McIlwrath

I was struggling to find a small example that could get my point across. Usually confusion due to mutable states arise in larger systems with many pieces, in my experience.

And yes, I agree your example is better code and more readable. But your code uses immutability to improve readability, so how does that support the idea that mutability doesn't decrease readability?

Thread Thread
 
jwp profile image
John Peters • Edited

Good point, the code example was only trying to point out that we don't need to be forced into an immutability model all the time, rather with proper techniques we can mutate at will and not worry about problems down the road.

But thanks for pointing this out. I'm beginning to see that javascript programmers may not follow the techniques listed above, so the "immutability" rules are good. Immutability is a creedo, a warning about what happens when mutating objects by reference where not intended. Always a bad outcome.

Collapse
 
miketalbot profile image
Mike Talbot ⭐

A very good point from my perspective on legibility. A key thing here is using stack based variables that have super low cost, so no point following my argument there. You can have many stack based variables without fearing a garbage collection glitch or a complex memory management operation.

So for clarity on my comment in light of this point, I'm suggesting that:

    something.state = {...something.state, x: something.state.x+1}

If you do it frequently is a bad idea for performance reasons due to the amount of memory being stored up for collection. It's also a bit less legible than:

   something.state.x += 1