Immutability – Practical Functional Programming | Part 3

25 July 2022[updated]

4 min read

Functional programming

Immutability stands at the core of Functional Programming. Together with pure functions, this principle unlocks the possibility to narrow your focus to each function implementation regardless of the global context. In this article, we are going to learn what Immutability means, what is an immutable collection, and how can you use immutability effectively.

Practical Functional Programming step by step is a series in which we are going to uncover the principles of functional programming and effective coding one small piece at the time.

Starting from a complete code example, we are going to learn step by step what makes a good functional code and what are the benefits of applying a functional programming paradigm to your codebase.


Immutability: What is that?

Practically speaking, immutability means that all your variables are constant (and not 'variable' 💁🏼‍♂️).

Depending on your background, you may be thinking: how can I create an application if everything is constant and cannot change? That's where Immutability comes into play.

Instead of changing the value of a variable in-place, you create a new constant derived from the value of one or more other constants. You never modify anything, you create new instances from previous values.

Why everything constant?

Think about the source of most of your problems while coding. Usually, most of the issues come from a variable which does not have the value you expect it to have. You are then required to start a long debugging process, inspecting intermediate values to spot where the incorrect update originated.

Well, what if instead no update is allowed? No need of debugging (most of the times at least). With Immutability, since every instance is a constant, you always know what is the value contained inside a variable and where it originated.

Local reasoning

Immutability (and pure functions) unlocks local reasoning. You do not need to check the current state of the application, the global context, the current value of every variable. You can reason about a single function. All you need to know is the inputs and the output.

This assumptions open a world of possibilities. More testable functions, maintainable code, and scalable applications. Every developer's dream!

Practical Example

We can clearly see Immutability in our code assignment example:

Map<String, int> buildmap(String str) => str.split('').foldLeft(
      <String, int>{},
      (acc, x) => {
        ...acc,
        x: (acc[x] ?? 0) + 1,
      },
    );

The only variable at our disposal is str. Since we want to follow the principles of Immutability and pure functions, we cannot change the value of str in any way nor access any global variable.

We are instead going to create a new variable from str. In the code we use two function that preserve the original value:

  • split: splits str based on the given character, and returns a new List<String>
  • foldLeft: takes the List<String> and returns a new value of type Map<String, int>

I emphasized the word new. Both functions are pure. Both do not update the original variable but simply return a new variable derived from the given one.

The same is for both Haskell and Typescript:

const buildmap = (str: string): Map<string, number> =>
  pipe(
    str.split(''),
    reduce(new Map<string, number>(), (acc, x) =>
      pipe(
        acc,
        modifyAt(eqString)(x, (n) => n + 1),
        O.getOrElse(() => pipe(acc, upsertAt(eqString)(x, 1)))
      )
    )
  );
buildmap :: String -> Map Char Int
    buildmap =
      foldl
        ( \acc x ->
            if member x acc
              then adjust (+ 1) x acc
              else insert x 1 acc
        )
        empty

Even functions like modifyAt and insert are Functional Programming and Immutability friendly. They do not update the source variable, but they return a new updated instance of the original value.


Now you know about Pure Functions from Part 2 and Immutability from Part 3 of this Practical Functional Programming series. If you adhere to these two principles all the times, your code will evolve to a new level. Do not trust me on this, just try it yourself and see it with your eyes!

See you soon on Part 4!