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.

Show Comments