DEV Community

Cover image for What Do You Think About Immutable Data?

What Do You Think About Immutable Data?

Miklos Bertalan on February 14, 2019

A few things are becoming de-facto solutions in JavaScript with a lot of tutorials but without much discussion about the practical benefits and rea...
Collapse
 
solkimicreb profile image
Miklos Bertalan

I think immutable data is okay on the backend but overused on the front-end. I like the simplicity and terseness of mutable objects when I have to deal with a lot of (short-term) persistent state and I never had issues with using mutable data. Of course, I have to pay attention to not mutate others' objects and I use immutable updates in these cases. Switching to a 100% immutability is an overkill though.

I also blame the broader phenomenon of thinking about code and not about products on immutable data and other trending patterns. I think people should worry much less about code beauty and much more about the product they are building. The massive amount of 'pattern propaganda' is making this difficult though.

On the other hand, I admire the elegance of immutable data - paired with functional programming - and I am glad I learned the foundations. For me, it's similar to the math I learned in university. I think it improved me in many fields but I rarely use it directly.

Collapse
 
_sburnicki profile image
Stefan Burnicki

In my experience, immutable data often reduces the amount of functions with side effects.
Side effects make testing hard, the code hard to understand and error prone.
So I experienced increasing quality with immutable data.

It's a pity many languages like JavaScript don't enforce immutability of objects easily.

Collapse
 
solkimicreb profile image
Miklos Bertalan • Edited

I think difficult testing is a valid counterpoint against mutable data (and side effects in general).

I never experienced decreased code readability though and I see it mentioned a lot. I always wonder if this is something that people say because of some influencers' opinion. I would say that I have a more difficult time following immutable logic as it tends to be longer / more fractured in JS. This is especially true when the involved data is persisting in memory for a longer time.

Edit: Thanks for the comment btw

Collapse
 
avalander profile image
Avalander • Edited

Regarding mutable data and readability, I think it's fine to have mutable data inside a small and short-lived scope.

The readability issues come when you have mutable data in a large scope, imagine a variable that can get mutated and reassigned at several points in a 100 line function with several levels of indentation, or across multiple function calls. When I read such code, I find it hard to keep track of all the mutations that happened to that variable and its current state.

Thread Thread
 
solkimicreb profile image
Miklos Bertalan • Edited

I understand the pain but I don't think immutability helps in this case.

You have a big chunk of data sitting in the global scope and you have to update it either with mutations or with immutable updates. The difference between the two is that the first case keeps the reference while the latter changes it (it replaces the whole object). The shape of the data changes either way and it's totally okay since it was the goal of the whole snippet.

Manipulating persistent data is a difficult thing to do, probably that's why we have so many state management libs on the (very stateful) modern front-end side.

I honestly think that in this case, it's a draw. Immutability and mutability are both nothing compared to the complex business logic / state structure you work on. Could you provide an example where immutable updates make things clearer when you have to work with data in a large scope?

EDIT: I may have misunderstood you. How do you usually deal with data on the front-end side?

Thread Thread
 
avalander profile image
Avalander • Edited

I must say that I lean strongly towards a declarative functional approach in the way I reason about code structure, and I don't often see the need to keep and mutate intermediate data. Depending on your experience and preferences, you might not see the need for immutable data at all.

I like to think about my applications as data flowing through a pipe of transformations. The whole application, as well as any component in it, can be understood as (current_state, input) -> new_state, i.e., it receives the current state and any input that warrants a "change" in the state, and returns an updated version of the state. It does not keep any internal state, and it does not mutate any global state. The intermediate transformations are just output from one part of the application that is sent as input to the next part of the application. In a way, I just think about what input a function needs, what output it produces, and how to compose several functions to apply to the data all the transformations I want.

In short, I like to keep a copy of the state, send it to the application when there is any input that might change it, then collect the new state that I get at the end, and send that new state to be processed when there is a new input.

Truth be told, writing code with this style in mind is harder than good ol' imperative code. It forces you to put a lot of thought into which parts of the application need what data and how the data will flow there. But I think it's worth the effort because the end result makes following what happens to the data very easy.

I do agree with you that just replacing mutable data structures with immutable ones in the same codebase won't cut it. Moreover, it will probably make things unnecessarily more complex. You need to design and structure your code around the data flow for my approach to work.

More specific to the frontend side, I really like the Elm architecture, and I use Hyperapp a lot. It's basically the Elm architecture adapted to Javascript, and it is a really simple and powerful framework.

Thread Thread
 
solkimicreb profile image
Miklos Bertalan

Thanks, it's a lot clearer now. I am moderately familiar with Elm and Hyperapp (never used them but read a few articles/docs about them). I use Redux often though which I think is pretty similar (correct me if I am wrong.)

My main concern with this kind of functional front-end pattern is best explained with a short story:

I am a maintaining a transparent reactive state management lib for React (which re-renders comps on relevant data mutations) and a big chunk of the issues are about very basic operations - like getting / settings values or applying (async) side effect. I always explain to simply use obj.prop, obj.prop = value, and async functions which they are all already familiar with. They are too afraid to even try them though because of their functional background.

I really like to 'hack around' with the language and I usually feel bad when I am forced into a set of best practices. When I work with immutable libs I often have to check their API for specific things while using mutable data requires me to do this far less often. I can play around a lot more. Obviously, this is not always true but this is my gut feeling.

Collapse
 
nektro profile image
Meghan (she/her)

have you seen Object.freeze?

Collapse
 
_sburnicki profile image
Stefan Burnicki

Indeed I didn't, thanks!
It even seems to be supported by typescript as the type definition returns a ReadOnly (stackoverflow.com/questions/486298...).

Makes me wonder why libraries like ngrx don't take advantage of this.

I will definitely try to use that one.

Collapse
 
dance2die profile image
Sung M. Kim

Robert "Uncle Bob" Martin discussed three programming paradigms in his book, Clean Architecture.

  1. SP (Sturectured Programming)
  2. OOP (Object-oriented Programming)
  3. FP (Functional Programming)

Each one provided programmers "constraints".

  1. SP - A direct/explicit transfer of control (goto is discouraged)
  2. OOP - An indirect transfer of control (function pointers are eliminated)
  3. FP - A variable assignment (cannot assign a new value one initialized)

To understand why immutable data is being popular,
let's see how aforementioned "constraints" help us.

  1. SP - with removal of goto in our code, we can see flow of our code better
  2. OOP- We have a complete control over code dependencies and flows
  3. FP - All race/deadlock conditions, concurrent update problems go away

Front-end world has evolved very quickly and requires many code to run in parallel or asynchronously.
Websites aren't just about onClick-do-this simple any more.
IMHO, immutable data became popular to keep our state in consistent state & also provide other benefits such as memoization, time travel, etc

Collapse
 
solkimicreb profile image
Miklos Bertalan • Edited

Thanks for the awesome comment. I really enjoyed it as a whole but I will highlight a few parts for further discussion if you don't mind.

"to keep our state in consistent state"

This is a bit vague for me. How is mutable state inconsistent?

"All race/deadlock conditions, concurrent update problems go away"

This is true for multi-threaded languages but I don't think this is an issue in case of (the mostly single-threaded) JavaScript.

"also provide other benefits such as memoization, time travel, etc"

I totally agree with this one, immutable updates really shine here.

PS: Thanks for the link to the book.

Collapse
 
dance2die profile image
Sung M. Kim

Thanks for the feedback, Miklos.

This is a bit vague for me. How is mutable state inconsistent?

What I meant was variables don't change thus wherever you access that variable, it's always the same (consistent).
Maybe it came out wrong 😅

Collapse
 
tiguchi profile image
Thomas Werner • Edited

I ran into situations where mutable data structures were biting back. For example, I wrote an event bus subscriber and I was holding on to an event data structure my subscriber received. Strange things happened down the line, since the event object was actually mutable and internally reused by the sender for performance reasons. The unexpected changes of the event data caused erratic behavior. That was a bit of a head scratcher and took me some digging in 3rd party library source code to figure out.

I guess doing something like that makes sense in Java land in order to avoid heavy garbage collector activity, if a ton of events are published in quick succession. But it should be clearly documented :-D

Another example are "fluent builders" (builder objects that allow you to configure a result with chained configuration method calls). I really dislike builders that are internally mutable and update their internal state after each call. I prefer builders that return an immutable "stage" snapshot of the current configuration. I think that's more flexible if you want to preconfigure a builder with some common settings and use that same builder for various different purposes.

Yet another (Java) example for mutable objects that can break expectations are data structures that are used as map keys. If the same key object is modified after inserting into a map data structure in a way so equals and hashcode produce different results then that could cause some unpredictable outcomes depending on the map implementation. Probably in best case a crash. In worst case map data corruption / loss.

Personally I try to make my data structures compact and immutable if possible to prevent weird things from happening :-)

Collapse
 
oscherler profile image
Olivier “Ölbaum” Scherler

Sometimes immutable data makes the code easier to write. I had a piece of code that received a large-ish nested structure (like the page hierarchy of a web site) and needed to walk through it, add properties to each item (a unique, incremental ID, and a sequential number that resets on each level), while building some other structures about it (e.g. an ID map).

It was initially done with foreach loops with the loop variable passed as reference, to transform the structure in-place, which seemed to be easier. It turned out to be a nightmare to read, modify and debug.

In the end, inspired by the way this kind of thing is typically done in Erlang, I rewrote everything recursively with pure functions that take as arguments the data left to process and the part of the result that was already processed, and return a new copy of the structure instead of modifying it in place (of course, it means you are often returning several values in an array, for the result, the rest of the yet-unprocessed data, and some carries like the last used ID or current sequence value—you typically use a tuple in Erlang).

To my surprise, it turned out to be much, much easier to write, read, and debug than the initial code, even though it seemed more complicated when you thought about it before starting (I was expecting a bit easier, but not by that amount).

Collapse
 
liutkin profile image
Volodymyr Liutkin

I don't get the trend of immutability just for the sake of immutability. If it's there, then it should solve some particular problem. And if it doesn't, the code will end up complicated heavily for no reason. The cost is huge.

Collapse
 
awwsmm profile image
Andrew (he/him)

I like to think that there's no such thing as mutable data. Data doesn't change, it just gets replaced by new data. Like measuring the temperature over the course of the day, the "old data" is still valid for the "old time", while the "new data" is valid for the "new time".

Some logbook software, for instance, will never let you truly delete anything that's ever been committed to the logbook. There will be a record in the book of the old data, a record of the fact that you "deleted" it, and a record of the fact that you replaced it with something new.

Immutable data like this offers lots of advantages in programming in terms of type checking, cached function evaluation, etc., but it also -- of course -- makes the job of the programmer a bit more difficult.

Collapse
 
caseycole589 profile image
Casey Cole

Use Mutable Data when performance matters and concurrency isn't the answer or when it just makes life a little bit simpler (Two Way Data Binding A Form Input)