Why Should Data Be Immutable?

In functional programming, data is supposed to be immutable, meaning once a variable is defined, you should not change its value.

But things change in the real world, how can you write useful code with data that never changes?

Easy, you simply create new values instead. Here's an example:

I'm a huge fan of Homestuck. One of my favorite characters, Terezi, types and speaks in a peculiar way. She always uses 4LL C4PS, 4ND SUBST1TUT3S A, I, 4ND E W1TH 4, 1, AND 3 R3SP3CT1V3LY. 1T'S T1M3 TO M4K3 4 T3R1Z1 SP34CH CONV3RT3R.

var _ = require('lodash/fp')

var caps = (string) => string.toUpperCase()  
var replace = _.curry(  
    (regex, replacement, string) =>
        string.replace(regex, replacement))
var a_to_4 = replace(/a/ig, '4')  
var i_to_1 = replace(/i/ig, '1')  
var e_to_3 = replace(/e/ig, '3')

terezi_speak = _.compose(e_to_3, i_to_1, a_to_4, caps)

console.log(terezi_speak("What's up homey?"))  

This will print out WH4T'S UP HOM3Y?

(I talk about _.compose and _.curry in this blog post).

One of the really cool things about this code is that no data was ever mutated. The thing is, you don't actually have to mutate data to write fully-functional programs. When data is supposed to change, just create a new value and pass it along.

So, why would you want to write programs this way?

Mutating state is the cause of a lot of bugs.

Let's say you've got these functions in your program:

function apply_discount(purchase, discount) {  
    purchase.price -= discount
}

function change_price(purchase, new_price) {  
    purchase.price = new_price
}

Now, let's say that in some other part of your program the price is changed:

change_price(purchase, '$300')  

And, later on and elsewhere, a discount is applied:

apply_discount(purchase, 10)  

The result of these two function calls is that purchase.price is NaN.

I know you've seen code like this.

Because state is shared, it's much easier for various parts of a system to monkey around with it.

Yes, encapsulation in object-oriented programming helps with this problem, if it's done right.

Avoiding mutation eliminates it:

var purchase = {  
    item: 'hover board',
    price: 1000,
    customer_name: 'Marty McFly'
}

var apply_discount = _.curry((discount, purchase) =>  
    ({
        item: purchase.item,
        price: purchase.price - discount,
        customer_name: purchase.customer_name
    }))

var change_price = _.curry((new_price, purchase) =>  
    ({
        item: purchase.item,
        price: new_price,
        customer_name: purchase.customer_name
    }))

console.log(_.compose(  
    apply_discount(10),
    change_price('$300'))
        (purchase))

The error is still here in this code, but there's only only place to look, one trail to follow. A bit of console.loging will show where the code breaks.

In fact, with composition this is much easier. Check it out:

var tap = (x) => {  
    console.log(x)
    return x
}

_.compose(  
    tap,
    apply_discount(10),
    tap,
    change_price('$300'))
        (purchase)

This will print out:

{ item: 'hover board',
  price: '$300',
  customer_name: 'Marty McFly' }
{ item: 'hover board', price: NaN, customer_name: 'Marty McFly' }

Now we know exactly where the problem lies and we didn't have to look all over the application to find it.

By making sure that transformations of data only happen by creating new values makes bugs easier to track and code less likely to break.

But isn't this computationally expensive?

It depends on what you're doing, but chances are good the answer is no. If the answer is yes, you can use a library like Immutable.js. More on that later...

Looking for a software developer?