Handling change in an immutable world

Peter Bayne

My name is Pete, and I’m addicted to F#.

I admit it — I love using functional languages and, given my .Net back-ground, particularly F#. The simplicity and above all readability puts it at the top of the heap. Most of what I’ll be talking about generally applies to most functional languages. I’ll use F# in my examples, but anyone familiar with the ML family should be able to follow along without a problem.

One of the founding concepts of the language and on the ML family of functional programming languages (e.g. Haskel, OCaml,…) is the immutability of variables (the name of which is now pretty ironic as they don’t vary anymore). Immutability makes a lot of things easier for me as a programmer — no pass-by-ref accidental value changes, automatic thread safety, one name = one value within scope.

This is all very beautiful and easy in the examples where we grab some data from a cool data source like the Star Wars social network, play with it a bit, and display it. However, things are pretty boring without any interaction, and interaction usually means changing (or at least saving) some data.

But, how do I interact with the program to change immutable data?

This is a question that I struggled with when coming to grips with this way of programming so I am documenting my discoveries for my own benefit, and sharing it for the benefit of those on a similar journey.

The standard answer to the above question is you don’t. Instead you make a new object that is like the old object but different.

person {                 olderPerson {                 
name = “Fred” --> name = "Fred"
age = 42 age = 43
} }

This is where F# can help us with the first technique for handling change…

Record constructors — Just like that one but…

F# has a concept of a record, which is similar to a struct in C# in that:

  • It’s immutable (funny that)
  • It is a value type (not passed by reference)
  • It can be compared to other records of the same type for equality by comparing its members’ values (i.e. compare the name and the age values)

Records have a simple definition…

type Person = { name: string; age: int; rating: double }

… and a simple instantiation.

let p1 = { name = "Pete"; age = 42; rating = 0.0 }

This is all good, but what about handling change? The record also has an alternate constructor that goes like this:

let p2 = { p1 with age = 43; rating = p1.rating * 1.2 }

This quickly lets you create a complex object that is a copy of the original with a few tweaks. You can even reference the original object within the constructor, as I did with the rating value. This is particularly useful for appending to contained lists.

Of course, now you have two values; the original one and the “changed” one. You can just ignore the original and let it eventually fall out of scope. Although, sometimes it is useful to have a breadcrumb trail of changes for logging / auditing / debugging purposes.

What’s next

In the next little while I will move on to a few other discoveries and up the complexity a bit. Some topics will include:

  1. Record constructors — Just like that one only different (this post)
  2. The OO way: implementing property setters in F# to “cheat” by creating a C#-style mutable object.
  3. Lenses: changing the thing inside the thing inside the other thing.
  4. Lenses and lists: changing the thing amongst the other things in that thing…maybe
  5. Two-way Type Providers: reflecting changing external data
  6. Some common things that usually rely on mutation: like sorting…

Peter Bayne

Written by

Principal Consulting Software Developer at Haumohio.com, based in beautiful Christchurch, NZ. Writing about devops, F#, and software dev in general.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade