4.2

Immutability

In the Value section, we learned that a value in Elm is anything that can be produced as a result of a computation. We also learned that Elm values are immutable, which means once created, we can never change them. Once again, Elm has borrowed this concept of immutability from mathematics. For example, the number 3 in mathematics is a value that cannot be changed. Sure, we can add or subtract other numbers from it, but 3 itself will never change. If we add 1 to 3, we get 4 - a completely new number. This is exactly what happens when we try to modify a value, such as a list, in Elm.

Immutable Constants

All constants in Elm are immutable. Once assigned, a constant cannot be reassigned a different value in the same scope. If you are coming to Elm from an imperative language this might sound confusing. In those languages, it’s common to write code like this:

var x = 1;
x = x + 1;

It means assign x as a name to an expression that evaluates to 1. Then take the current value of x, add 1 to it and assign the result back to x essentially mutating its original value. Let’s try that same code in Elm to see what happens.

> x = 1
1

> x = x + 1

-------------------------- BAD RECURSION -----------------------------
`x` is defined directly in terms of itself, causing an infinite loop.

5| x = x + 1
   ^
Maybe you are trying to mutate a variable? Elm does not have mutation,
so when I see `x` defined in terms of `x`, I treat it as a recursive
definition. Try giving the new value a new name!

Maybe you DO want a recursive value? To define `x` we need to know what
`x` is, so lets expand it. Wait, but now we need to know what `x` is,
so lets expand it... This will keep going infinitely!

As usual, Elm provides a descriptive error message pointing out that it doesn’t allow mutation. Definitions like x = x + 1 are illegal in both algebra and Elm because we already defined x as 1 and now we are saying that x is 2. It can’t be both at the same time.

If you pay close attention to the error message, Elm is actually trying to tell us why it can’t allow mutation in this case. When we try to evaluate an expression such as x = x + 1, we’re essentially asking Elm to create a constant named x that is defined in terms of itself. So Elm will try to expand the definition further as shown below creating an infinite loop:

Recursive definition
x = x + 1
x = (x + 1) + 1
x = ((x + 1) + 1) + 1
x = (((x + 1) + 1) + 1) + 1
x = ((((x + 1) + 1) + 1) + 1) + 1
...

Elm also tells us that we can fix this by giving the x + 1 expression a new name like this:

> x = 1
1

> xPlusOne = x + 1
2

In an imperative language, x is appropriately called a variable because its value can vary even after its creation. Whereas in Elm, x is a constant. Because Elm doesn’t allow mutation, the language has no variables. Does this mean we can’t use the same name to represent other values anywhere else in our code? Not really. Once assigned, a constant cannot be reassigned to a different value only in the same scope.

Scope
A scope is a region in a program where defined constants exist and are accessible. Constants created in a region cannot be accessed outside of that region. As soon as the program execution leaves this region all constants and values created in the region will be destroyed.

Constants like x are local to their scope so their life is usually short. When their scope is no longer alive, they will be discarded. After that we can reuse the name to represent other values. The function definition shown below is a good example of a constant in a limited scope. Go ahead and add it right above main in Playground.elm.

multiplyByFive number =
    let
        multiplier =
            5
    in
        number * multiplier


main =
    ...

Now apply multiplyByFive in main.

main =
    multiplyByFive 3
        |> toString
        |> Html.text

If you don’t have elm-reactor running already, run it from the beginning-elm directory in the terminal. After that go to http://localhost:8000/elm-examples/Playground.elm on your browser and you should see 15 rendered on the screen.

As we learned in the Let Expression section, let expression is one of the ways we can create a local scope in Elm. The multiplier constant in multiplyByFive function is bound to 5. As soon as the program execution steps out of the let expression, multiplier won’t be alive anymore. If we try to reassign a different value to it inside the let expression Elm will complain. Replace the multiplyByFive function definition in Playground.elm with the following code.

multiplyByFive number =
    let
        multiplier =
            5

        multiplier =
            6
    in
        number * multiplier

If you refresh the page at http://localhost:8000/elm-examples/Playground.elm, you should see the following error.

But if we move the second definition of multiplier out of the multiplyByFive function, Elm doesn’t complain anymore because it’s now defined in a different scope than the one that resides in the let expression.

multiplier =
    6


multiplyByFive number =
    let
        multiplier =
            5
    in
        number * multiplier

If you refresh the page http://localhost:8000/elm-examples/Playground.elm, the error should be gone and you should see 15 rendered on the screen. Go ahead and remove the multiplier = 6 definition. Now let’s try the same thing in the repl.

> multiplier = 5
5

> multiplier = 6
6

Hmm… why does the repl allow us to reassign a different value to multiplier? That’s because the repl works a little differently. Whenever we reassign a different value to an existing constant, the repl essentially rebinds the constant to a new value. The rebinding process kills the constant and brings it back to life as if the constant was never pointing to any other value before. Without this rebinding process, it would be difficult to try things out in the repl.

Immutable Collections

In the Modifying Tuples section, when we were trying to understand why we couldn’t modify tuples, we discovered that all collections in Elm are also immutable. Because it’s hard to build anything useful if we can’t transform data from one form to another, Elm uses a clever technique to give us the appearance of adding or removing values from a collection.

> numbers = [ 1, 2, 3 ]
[1,2,3]

> 0 :: numbers
[0, 1, 2, 3]

> List.drop 1 numbers
[2,3]

In the example above, we used the :: operator to add 0 to the beginning of the numbers list and used the drop function to remove the first element from the same list. Although we were able to add and drop values from the list, its original content has remained intact.

> numbers
[1,2,3]

Elm created a copy of the original list, added 0 to it, and returned it as the result. While this was all happening, the original list remained unchanged.

“In a purely functional program, the value of a variable never changes, and yet, it changes all the time! A paradox!” - Joel Spolsky

Performance Implications of Immutability

Creating a new copy of data whenever we update it sounds like an expensive operation from performance standpoint. This is a valid concern. However, Elm is smart and it knows that existing data is immutable. So it reuses it, in part or as a whole, when building new data. As a result, immutability doesn’t really incur any performance penalty in Elm. Here is how the internal representation of the new list from the example above looks like:

Benefits of Immutability

We have talked a lot about how constants and values are immutable in Elm, but what benefits do we get from it? The primary benefit of immutability is that it allows us to write programs that behave as expected. This leads to highly maintainable code. To make this more concrete, let’s compare an implementation in Elm with another language, for example JavaScript, that doesn’t have immutability baked in.

Back in the Sorting a List section, we learned how to sort a list containing the top seven highest scores (shown below) from regular season games in NBA history in different orders.

[ 316, 320, 312, 370, 337, 318, 314 ]

Let’s say the NBA has decided to allow a new performance-enhancing drug that has the potential to double each player’s scoring. This will make it hard to compare current players against players who have already retired. So we need to adjust all historical stats. Let’s write a function to do that. We will first write it in JavaScript — a language that allows mutation — to find out what could go wrong.

Don’t worry if you have never used JavaScript before. The example below is quite simple and you should be able to follow along without difficulty.

Create a new file called experiment.js in the root project directory beginning-elm.

Now add the following code to it.

var multiplier = 2;
var scores = [316, 320, 312, 370, 337, 318, 314];

function doubleScores(scores) {
    for (var i = 0; i < scores.length; i++) {
        scores[i] = scores[i] * multiplier;
    }

    return scores;
}

scores - A variable that points to a list of highest scores.

multiplier - A variable that points to a number that will be used to multiply each element in the scores list.

doubleScores - A function that takes a list of scores; iterates through each element in the list using a for loop; and multiplies them by a value held in the multiplier variable.

Next, load experiment.js in index.html located in the root project directory beginning-elm like this:

<!DOCTYPE html>
<html>
  .
  .
  <body>
    .
    .
    <script src="elm.js"></script>
    <script src="experiment.js"></script>
    .
    .
  </body>
</html>

As mentioned in the Elm Compiler section, it’s perfectly fine to use Elm and JavaScript code side by side.

Open index.html in a browser and then open the browser console.

Opening browser console
Instructions for opening the browser console depends on which browser you’re using. Please read this nice tutorial from WickedlySmart that explains how to open the console on various browsers.

Verify that the code in experiment.js is loaded successfully by printing the following values from the console.

> scores
[316, 320, 312, 370, 337, 318, 314]

> multiplier
2

Let’s see doubleScores function in action. Apply it like this in the console.

> doubleScores(scores)
[632, 640, 624, 740, 674, 636, 628]

So far everything looks good. Now let’s go ahead and redefine the multiplier variable in experiment.js so that it points to 3 instead.

var multiplier = 2;
var scores = [316, 320, 312, 370, 337, 318, 314];

var multiplier = 3;

function doubleScores(scores) {
    for (var i = 0; i < scores.length; i++) {
        scores[i] = scores[i] * multiplier;
    }

    return scores;
}

Notice the original definition var multiplier = 2; is still there. Now, reload index.html in the browser so our changes to experiment.js take effect. After that, apply the doubleScores function to the scores list by typing the code below into the browser console.

> scores
[316, 320, 312, 370, 337, 318, 314]

> doubleScores(scores)
[948, 960, 936, 1110, 1011, 954, 942]

doubleScores now triples each element in the scores list. This is problematic. Its new behavior does not match its name. We want it to only double the scores no matter what, so that other people reading our code aren’t confused by the mismatch.

Unfortunately, most languages that allow mutation suffer from this problem. It’s especially frustrating when the redefinition happens in a remote part of the code, which is hard to detect. At least in the example above, we can see that multiplier is being redefined close to the original definition and we can correct it. But in the real world, most problems caused by mutation tend to manifest after the code has been deployed to a production environment.

How does Elm deal with this situation? Well, let’s reimplement the code above in Elm to find out. Add the following code right above main in Playground.elm.

multiplier =
    2


scores =
    [ 316, 320, 312, 370, 337, 318, 314 ]


multiplier =
    3


doubleScores scores =
    List.map (\x -> x * multiplier) scores

If you refresh the page at http://localhost:8000/elm-examples/Playground.elm, you should see the following error.

It’s exactly the same error we got in the Immutable Constants section above. Elm doesn’t allow us to redefine multiplier in the same scope. Go ahead and remove the redefinition multiplier = 3.

The JavaScript code above has one more issue: it mutates the original scores list.

> scores
[316, 320, 312, 370, 337, 318, 314]

> doubleScores(scores)
[632, 640, 624, 740, 674, 636, 628]

> scores
[632, 640, 624, 740, 674, 636, 628]

As you can see the values inside the scores list have changed. This is also problematic. To understand why, let’s remove the redefinition var multiplier = 3 from experiment.js and add two new functions below the doubleScores function like this:

var multiplier = 2;
var scores = [316, 320, 312, 370, 337, 318, 314];

function doubleScores(scores) {
    for (var i = 0; i < scores.length; i++) {
        scores[i] = scores[i] * multiplier;
    }

    return scores;
}

function scoresLessThan320(scores) {
    return scores.filter(isLessThan320);
}

function isLessThan320(score) {
    return score < 320;
}

As its name suggests, the scoresLessThan320 function returns all scores that are less than 320. It delegates the task of checking whether a score is less than 320 or not to the isLessThan320 function. Reload index.html in the browser so that the new code in experiment.js will get loaded. After that apply the scoresLessThan320 function in the browser console like this:

> scores
[316, 320, 312, 370, 337, 318, 314]

> scoresLessThan320(scores)
[316, 312, 318, 314]

scoresLessThan320 gives us what we expect. But what will happen if we apply the doubleScores function before scoresLessThan320? To find out let’s reload index.html in the browser one more time so that we’re starting fresh. After that apply the functions as shown below in the browser console.

> scores
[316, 320, 312, 370, 337, 318, 314]

> doubleScores(scores)
[632, 640, 624, 740, 674, 636, 628]

> scoresLessThan320(scores)
[]

scoresLessThan320 reports that there are no scores below 320 which is incorrect. This is because doubleScores doubled the values and saved them back into the original list which we ended up passing to scoresLessThan320 without realizing that it has been changed from underneath.

What we really intended to happen was this:

Can this problem be avoided in JavaScript? Absolutely. Replace the current implementation of doubleScores with this:

function doubleScores(scores) {
    var newScores = [];

    for (var i = 0; i < scores.length; i++) {
        newScores[i] = scores[i] * multiplier;
    }

    return newScores;
}

doubleScores doesn’t modify the existing list anymore. Instead it always returns a new one. Refresh the browser and apply the doubleScores and scoresLessThan320 functions in the same order as before.

> scores
[316, 320, 312, 370, 337, 318, 314]

> doubleScores(scores)
[632, 640, 624, 740, 674, 636, 628]

> scoresLessThan320(scores)
[316, 312, 318, 314]

Now it works as expected. Let’s try this in Elm to see if we will get different results depending on which order we apply the scoresLessThan320 function. Add the following definitions right above main in Playground.elm

scoresLessThan320 scores =
    List.filter isLessThan320 scores


isLessThan320 score =
    score < 320


main =
    ...

To properly compare the results from the experiment we conducted with JavaScript code in the browser console with the Elm code, we need to load the code in Playground.elm into elm-repl. Currently, Elm looks for modules only in the beginning-elm directory because in the elm-package.json file we have specified "." as the only source directory. "." means the directory where elm-package.json itself is located which happens to be beginning-elm. For elm-repl to find Playground.elm, we need to add elm-examples to the list as well.

{
    .
    .
    "source-directories": [
        ".",
        "elm-examples"
    ],
    .
    .
}

Don’t forget to add a comma after ".". From the repl, load the Playground module.

> import Playground exposing (scores, doubleScores, scoresLessThan320)

We exposed scores, doubleScores, and scoresLessThan320 so that we don’t have to prefix them with the module name when we use them. Not having prefix makes it easier to compare our Elm code with the JavaScript code listed above. As mentioned in the Strings section, be careful not to run into name collision when exposing functions and values like this. Now let’s apply scoresLessThan320 before doubleScores and see what we get.

> scores
[316,320,312,370,337,318,314]

> scoresLessThan320 scores
[316,312,318,314]

> doubleScores scores
[632,640,624,740,674,636,628]

We get what we expect. Now let’s apply scoresLessThan320 after doubleScores has been applied.

> scores
[316,320,312,370,337,318,314]

> doubleScores scores
[632,640,624,740,674,636,628]

> scoresLessThan320 scores
[316,312,318,314]

Again, we get what we expect. We don’t need to implement scoresLessThan320 in a special way in Elm for it to behave consistently.

Although we were able to resolve the issue introduced by mutation in JavaScript by explicitly returning a new list, we had to be cognizant of the fact that immutability isn’t baked into JavaScript. That made us take extra precaution. This can get tiresome as the code base grows. However, in Elm List.map (and all other functions in the List module) always return a new list by default. Having immutability baked into the language itself allows us to completely avoid problems like these.

Back to top

New chapters are coming soon!

Sign up for the Elm Programming newsletter to get notified!

* indicates required
Close