Groovy Functional Programming - Immutability
In our drive to write Groovy code in functional style, we write pure functions as much as possible. Since mutation is not allowed in pure functions, immutable data become an important foundation.
If you are not familiar with pre functions, this article provides the necessary details.
Immutable Collections
Let us see what Groovy provides for us to write immutable collections.
Java Collections
class provides a bunch of methods to create immutable collections.
List<Integer> numbers = [1, 2, 3, 4]
Set<String> fruits = ['Orange', 'Apple', 'Grape'] as Set
Map<String, String> capitals = [India: 'New Delhi', Nepal: 'Kathmandu']
List<Integer> unmodifiableNumbers = Collections.unmodifiableList(numbers)
Set<String> unmodifiableFruits = Collections.unmodifiableSet(fruits)
Map<String, String> unModifiableCapitals = Collections.unmodifiableMap(capitals)
unmodifiableNumbers << 10 // throws UnsupportedOperationException
If you attempt to modify these, UnsupportedOperationException
will be thrown. However note that in order to create an unmodifiable version of the collection, we need to know if the collection is a List, Set or Map and call the appropriate flavour of the method. Groovy provides a convenient wrapper around these methods. Let's refactor the above code using what Groovy offers.
def unmodifiableNumbers = numbers.asImmutable()
def unmodifiableFruits = fruits.asImmutable()
def unModifiableCapitals = capitals.asImmutable()
println unmodifiableNumbers.class // class java.util.Collections$UnmodifiableRandomAccessList
println unmodifiableFruits.class // class java.util.Collections$UnmodifiableSet
println unModifiableCapitals.getClass() // class java.util.Collections$UnmodifiableMap
Since Groovy 2.5.0, you could also use asUnmodifiable
instead if asImmutable
as follows.
def unmodifiableNumbers = numbers.asUnmodifiable()
def unmodifiableFruits = fruits.asUnmodifiable()
def unModifiableCapitals = capitals.asUnmodifiable()
Groovy also provides developer with the option to control the mutability for several operations. If you invoke numbers.sort()
, the list numbers will be sorted in place. If you invoke as numbers.sort(false)
, the original list numbers will not get mutated.
def scores = [80, 60, 70]
def scoresSorted = scores.sort(false)
println scores // [80, 60, 70]
println scoresSorted // [60, 70, 80]
A few operations that support boolean mutate
argument
- unique
- reverse
Immutable Objects
@ToString
class Point {
int x
int y
}
Point somePoint = new Point(x: 10, y: 10)
println somePoint // Point(10, 10)
somePoint.x = 5
somePoint.y = 5
println somePoint // Point(5, 5)
In the above example, objects of Point
class are mutable. We could change the values of x and y properties. To make the objects immutable, we can use the groovy.transform.Immutable
AST transformation provided by Groovy.
@Immutable
@ToString
class Point {
int x
int y
}
Point somePoint = new Point(x: 10, y: 10)
println somePoint // Point(10, 10)
somePoint.x = 5 // groovy.lang.ReadOnlyPropertyException: Cannot set readonly property: x for class: demo.Point
Upon seeing Immutable
AST transformation, Groovy compiler will make the fields final and it will not generate setters for the fields. Hence if you attempt to modify the fields, you will end up with groovy.lang.ReadOnlyPropertyException
. Now you can be confidently pass the instance of Point
class around functions, without worrying they getting modified accidentally.
You may want to take a look at the slides from my FunctionalConf talk for more examples.