Scala Saturday: Dealing with Immutability (2 of 2)
The management and isolation of mutability
In part 1 we learned about the val
keyword in Scala which allows you to create a value that cannot be changed after declaration. Let’s look at how to use this in scenarios where we would previously use mutability.
Validation
Here we have two functions that return the same value, a user’s name that has been trimmed. They also share the same structure: initialization, “doing stuff”, and returning values (where the last expression of a method is its return value, without requiring a return
keyword). However, the two differ in how they get to that return value.
The first method takes the variable, assigns it a new copy from the trim
method, and returns the same variable. In the second method the userInput
value cannot be changed after declaration. So what do we do? We declare another value that trims the string the same way and return that new value, as opposed to returning the initial value.
Yup, it’s that simple. We can model changes to values by introducing new values. If we had several steps of validation, we could have a new value for each step. We have the advantage of documenting what each step is doing through the value names. Compare the following.
In the first method, we know that there’s something going on, but none of it is labeled. What is the method returning? Maybe we could solve that with comments. In the second method, no comments are needed. Each value name communicates what it contains. And if you look closely, you can see a small, linear dependency graph. If we reordered the mutable operations in the first example, we couldn’t be sure if we would get the same result. But if we tried to reorder the immutable operations using val
, a compilation error would stop us because the expression sides of the values also refer to values, making their relationship to one another much more clear and explicit.
Now that you have both styles at your disposal, which one should you use? The Scala community already has this one covered.
Prefer
val
overvar
.
Or more generally,
Prefer
immutability
overmutability
.
In an expressive, multi-paradigm language like Scala, there can be multiple ways to write a solution. The bevy of options can turn into a liability if left unchecked, which is why we have the guidelines above. Notice that it says “prefer” versus “you must use”. Each style has benefits and drawbacks. The mutable style is handy for performance critical loops or holding state, like a counter. The immutable style tends to generate more intermediate structures that need to be garbage collected. But the immutable style is often easier to understand and easier to test. 99% of the time you will want to start with val
and immutable patterns, and then move to var
and mutability for exceptional cases.
Collections
Let’s see how our new immutable outlook on life works with collections of values. Imagine that we have directions
that need some preparation before they get used. We need to add the values "start”
and "end"
to the directions
.
In the mutable example, we only have one variable that is being used and reused. This works, but we would prefer not to use this style. The second example uses a different set of operators that return copies of directions
with new information added, while leaving the original collection untouched.
It may be difficult to see, but the approach to avoiding mutability here in the collections example is solved in the same way as the user name example by creating new copies of the data instead of modifying the originals.
Note that the name of the class that holds mutable data must be referred to by a longer name (or imported manually), adding a bit of friction to its usability. We can say that Scala prefers its immutable and read-only structures to its mutable ones. If you choose to use mutability, you want to call attention to it as a warning sign to others.
Scala’s dirty secrets
When I first learned Scala, I jumped on the immutability wagon right quick.
The king has deemed it so, everything shall be immutable! We must destroy the mutable evil!
But it’s not good to be dogmatic about things like that (or anything, really). Mutability is a tool in our toolbox that has its place. Here are some places in the Scala standard library that use mutability.
The two methods above are slightly modified copies of methods in Scala’s collections library. The first counts the number of eligible elements in a collection given some filter (or “predicate”) p
. Here, we are counting the even numbers between 1 and 10, inclusive. The second method collapses the list of numbers into just one number using simple addition (and something called “a zero”, which in this case is literally the number zero).
These two methods and the family of methods just like it all depend on mutability. We could rewrite some of them to use immutability, but the immutable solutions would probably have different performance characteristics. Here, the Scala authors have opted to use the mutable style for performance and flexibility.
Also note that these methods act immutable in a way. Neither of them modify the original collection; they only modify some state internal to their functionality and return the result. No one in the outside world knows if they used immutability or not. The mutability is well contained in each method. There’s no risk of outside forces changing the counter c
or result variable res
. The mutability is in a tidy package that can be safely mixed with immutable code. By managing the boundary between mutable and immutable code, we’re turning mutability’s liability into an asset that we can take advantage of freely. Therefore immutability isn’t about the eradication of mutability, but more its management and isolation.
Immutability is really about the management and isolation of mutability.
Eventually there will come a time when you need to write mutable code. And you should not feel ashamed. The king will not smite you! We want to use mutability because it is a tool in our toolbox, but use it in a responsible way. We can take that mutability, wrap it in an abstraction, and tie a bow on it with Scaladoc on top. What was once unsafe is now a gift.
I hope you’ve enjoyed this two-part series on immutability. We’ve hit just the tip of the functional iceberg. Now go forth and code bravely and immutably. Until next time, happy hacking and see you on the next Scala Saturday!