DEV Community

Cover image for How to write IMMUTABLE code and never get stuck debugging again

How to write IMMUTABLE code and never get stuck debugging again

Douglas Parsons on November 17, 2020

I've written production code in a variety of different languages throughout my career, including Haskell, Scala, Go, Python, Java or JavaScript. Wh...
Collapse
 
carlyraejepsenstan profile image
CarlyRaeJepsenStan • Edited

I liked the walruses analogy! Gave me a good laugh.
If you don't mind my noob question, how would one iterate over an array/collection/vector, to create 100% immutable code? The usual loops would not be an option, as the iterator or counter variable needs to change.

Collapse
 
bytebodger profile image
Adam Nathaniel Davis

The answer is: recursion. In true Functional Programming (which heavily features immutability), there are no loops. People throw around terms like "functional programming" and "immutability", but they rarely think about what it takes to fully implement these features.

Everything that you can do with a loop, you can also do with a recursive function. Here's a simple example:

const countToTen = (iterator = 1) => {
  if (iterator > 10)
    return;
  console.log(iterator);
  const nextIterator = iterator + 1;
  countToTen(nextIterator);
}

countToTen();
Enter fullscreen mode Exit fullscreen mode
Collapse
 
carlyraejepsenstan profile image
CarlyRaeJepsenStan

Wow, thanks so much! This is exactly what I was looking for.

Collapse
 
dglsparsons profile image
Douglas Parsons

Hey, I'm glad you enjoyed the article <3.

That's a great question and thanks for asking. You're absolutely right about the iterator or counter variables changing. It's not something you can avoid really as it's so inherent to how loops work.

I don't have particular problem with that though (although in some languages you have to be careful, especially if writing asynchronous code that uses those variables). What's more important, in my opinion, is what you are doing in the iteration - are you mutating an array in place, or returning a new array? Using map and reduce where possible helps a lot.

Hope that helps!

Collapse
 
carlyraejepsenstan profile image
CarlyRaeJepsenStan

I see - that question has bugged me a lot while I'm coding. Thanks for the advice!

Collapse
 
winstonpuckett profile image
Winston Puckett

I totally agree. And to tack on to your point about immutability and performance, I've found that strange database queries are consistently the reason for slowness in my app. I look forward to the day when I'm at all concerned about how the code chooses to create and destroy objects

Collapse
 
dglsparsons profile image
Douglas Parsons

Is that using an ORM?

Collapse
 
winstonpuckett profile image
Winston Puckett

Hahaha... And there lies the problem with ORMs.

It is often more that database queries are repeated in odd spots when they don't need to be. It is sometimes the ORM's fault, but I like the Linq syntax so much I don't want to blame it all on that

Thread Thread
 
dglsparsons profile image
Douglas Parsons

I've not used Linq much, so I suspect I don't know what I'm missing out on. I'm a big fan of NoSQL though, partially because it doesn't let you query in inefficient ways.

Thread Thread
 
winstonpuckett profile image
Winston Puckett

I didn't know that. That's pretty cool

Collapse
 
vlasales profile image
Vlastimil Pospichal

ORM does not use immutability and that is why there are such problems with it.

Collapse
 
eecolor profile image
EECOLOR • Edited

Unless you are benchmarking and actively measuring performance, then you almost certainly shouldn’t care.

Yeah! The only situations I know I am using mutability is when I have to process tens of thousands of items, like this:

const index = array.reduce(
  (result, x) => (result[x.prop] = x, result),
  {}
)
Enter fullscreen mode Exit fullscreen mode

If it is below tens of thousands I will write this:

const index = array.reduce(
  (result, x) => ({ ...result, [x.prop]: x }),
  {}
)
Enter fullscreen mode Exit fullscreen mode

My rule is: if mutation makes the code significantly better to read or actually helps with performance, you can apply it locally.

So in code reviews I ask people to move stuff into functions that, from the outside, seem to be immutable:

function replaceAt(array, index, x) {
  const copy = array.slice()
  array[index] = x
  return copy
}
Enter fullscreen mode Exit fullscreen mode

The alternative would be something like this (more likely to have errors):

function replaceAt(array, index, x) {
  return [...array.slice(0, index), x, ...array.slice(index + 1)]
}
Enter fullscreen mode Exit fullscreen mode

Side note: if a language has an immutable construct in it's standard library I would prefer that.

Collapse
 
mrxcitement profile image
Mike Barker

Here is an interview with Robert (Uncle Bob) Martin talking about the book "Structure and Interpretation of Computer Programming" and his and the books take on immutability and its benefits. youtu.be/Z0VpFmp_q4A?t=148

Collapse
 
dglsparsons profile image
Douglas Parsons

That's definitely one of my favourite programming books of all time. Packed full of wisdom. Really interesting video and a great take on functional programming too! Thanks for sharing this.

Collapse
 
mcsee profile image
Maxi Contieri

Amazing Article!

Immutability is the only way we can guarantee code stands time.

I'll give you some pointers to follow:

maximilianocontieri.com/the-evil-p...

and NULLs that should be avoided as you clarified

maximilianocontieri.com/null-the-b...

and this article you point out is also excellent

doc.rust-lang.org/book/ch03-01-var...

We should push for MORE immutability on our objects

Collapse
 
dglsparsons profile image
Douglas Parsons

Thanks for the useful links, and glad you agree :).

Collapse
 
vsingh7 profile image
Vikram Singh

Great article! I've only heard about immutability in passing, but never really looked into it. Your post has me intruiged! Where can one go to find out how to write immutable code in their language of choice? I know I could easily Google this, but if you have some resources off the top of your head, I would greatly appreciate the direction :)

Collapse
 
dglsparsons profile image
Douglas Parsons

Hi Vikram, I'm glad you enjoyed the article and it's fantastic that you are intrigued!

For specific resources - I sadly don't have any off the top of my head. Personally, I feel like it's a change in approach to writing code as much as anything else.

For Javascript and Python, I'd definitely have a look into the spread operators though (for objects / dicts), and spreading in arrays for javascript.

Collapse
 
jhelberg profile image
Joost Helberg

Great and important article, well put. I was a bit confused by the title though. I really thought that you meant code which could not change; of course it should be bug free then, as fixing immutable code is impossible. But your text is about the objects, not the code.

Collapse
 
_hs_ profile image
HS

Using it mainly to avoid DATA corruption or in other words classes that represent data models are mostly immutable in my code.

State I like. I love being able to put something in a service (class or module or whatever the term in given language) where it only changes itself with all the restriction in place. There is no setter put things like next() or such which heavily do verification or so. This has saved me loads of time, shortened my code, made it more readable and clean and in one specific case made it more safe to multi-thread as it throws exception on unexpected behaviour which informs me that something else is trying to hit the data which I never expected it to do so. So basically in some ways it actually was crawler through code which would detect unintended thread switching with parallelisation. But regardless of the last one-time specific case I see no point in preventing state anyways. My connection drivers & driver pools have state and I expect them to do so to save some networking overhead. My registries for active objects (like special services) have such state and I love it. You could reset settings in runtime making code drop all active services and create new ones with the new properties sent via let's say HTTP - services may be immutable in that case but registry is not so again the state and mutability. There's plenty of reasons to use it and not bother yourself with monad, monoid, hemorrhoid...

I think people abused it too much so they're running away from it as much as possible (but also yeah theorist and their maths and such for which some of us don't care). Some combination of both worlds is always better in my books. It's like when you learn to use less memory and start hitting short or such in every language until you realise int actually works faster in some environments because it's running on x86-64 which has to do splitting so you have to pick memory over processor. Then you realise hey it's good we have both.

Collapse
 
sebastienlorber profile image
Sebastien Lorber

Interesting metaphor 🤪

I don't really understand the relation between nullpointer errors and mutations, that's worth expanding a bit with an example where a mutation does lead to such error.

Collapse
 
devoresyah profile image
DeVoresyah ArEst

always use immutability in every single of my react native project 🎉🎉🎉

Collapse
 
dglsparsons profile image
Douglas Parsons

Glad you find it useful. Javascript is so much nicer when you write immutable code. There are too many foot-guns otherwise!

Collapse
 
andreidascalu profile image
Andrei Dascalu

I always dislike the formulation "immutable code". Depending on understanding, code is either always mutable or always immutable.
But here we are talking about the data that the code manipulates.