Static Checker for Immutability in Kotlin

An immutable object is an object whose state cannot be modified after it is created. Immutable objects have several desirable properties, of which the two most important ones relevant to this post are:

  • They are inherently thread-safe: being read-only, they can be accessed safely from separate threads without having to worry about unexpected state or overwriting changes.
  • They can act as value objects: two value objects created equal should remain equal, which is guaranteed when they are unable to change state.

Therefore, it is common to implement objects as immutable, and software frameworks may require/expect polymorphic objects passed to their APIs to be immutable. Unfortunately, correctly implementing an object as immutable relies on the developer’s expertise in most mainstream OOP languages (it cannot be enforced by the programming language).

Data classes in Kotlin certainly simplify creating value objects: an equals, hashCode, and copy function are automatically generated. However, data classes are not immutable by default! Kotlin still allows defining var members on data classes, and adding members of mutable types.

True guarantees for immutability would have to be baked in to the programming language/compiler, or verified using a static checker. While proposals to support immutability in Kotlin exist, a fully functional static checker for Kotlin is already available—detekt.

Detekt contains a rule out of the box to verify whether all members in data classes are specified as val, but does not verify whether those members are immutable types. Furthermore, not all immutable objects should necessarily be implemented as data classes, and there might be cases in which mutability in data classes is desirable.

Having a need to enforce developers that extend base types in a framework I am working on as immutable and/or as data classes, I implemented a detekt plugin which enables verifying whether concrete classes are implemented as specified according to annotations applied to base types (e.g., @Immutable and @ImplementAsDataClass).

For example, the following implementation will warn NotImmutable is not implemented as immutable when running the static checker since Mutable.foobar is specified as var:

class Mutable( var foobar: Int ) 

class NotImmutable( val mutable: Mutable ) : Base

@Immutable
interface Base

The plugin is definitely not complete yet, in that it does not verify all cases which may be mutable, but it already catches a majority of errors and as an open-source contribution on GitHub I hope other might take an interest and contribute to the project.

List of Strongly-Typed Objects Acting Like Enum in Kotlin

Suppose you have a list of object instances (Kotlin’s concept for singleton classes) that are logically related to one another and you therefore want to group them together, but also want to provide direct (non-index- or iterator-based) access to them, similar to how you access an enum.

How would you go about doing that in Kotlin?

As an example, take the following DataType interface and implementing object Geolocation:

interface DataType { val typeName: String }

object GeolocationType : DataType
{
    override val typeName: String = "geolocation"

    fun create( longitude: Double, latitude: Double ) =
        Pair( longitude, latitude )
}

Imagine many more DataType‘s: WeightType, StepcountType, etc. Now you want to provide a list of SupportedTypes containing all the types your codebase supports, but you also want to provide direct access to that list, so that the create() method (and other potential type-specific members) for Geolocation can be called.

While enums in Kotlin are fairly powerful—they largely behave like normal classes and can implement interfaces—they do not support generic type parameters and (as far as I could figure out) enum values cannot be instantiated based on existing instances. You could let the enum implement the interface of the instances you want to represent and override all methods redirecting them to the wrapped instance, but:

  • This introduces an intermediate instance, which might not be desirable for equality checking.
  • Does not provide access to type-specific members, such as create() in the example given.
  • Leads to heavy code bloat which is no fun to maintain.
enum class SupportedTypes : DataType
{
    GEOLOCATION
    {
        override val typeName = GeolocationType.typeName

        // This method can't be accessed!
        fun create( longitude: Double, latitude: Double ) =
            GeolocationType.create( longitude, latitude )
    }
}

Instead, I opted to create the following base class …

open class EnumObjectList<T>
    private constructor( private val list: MutableList<T> ) :
    List<T> by list
{
    constructor() : this( mutableListOf() )

    protected fun <TAdd : T> add( item: TAdd ): TAdd =
        item.also { list.add( it ) }
}

.. and use it as follows:

object SupportedTypes : EnumObjectList<DataType>()
{
    val GEOLOCATION = add( GeolocationType )
}

This now allows to iterate all supported types, just like enums or a list, but also to get the full type information (including generics) when accessing the member directly:

val supportedTypeNames = SupportedTypes.map { it.typeName }

val data = SupportedTypes.GEOLOCATION.create( 42.0, 42.0 )

For a real-world use case, which this simplified example was based on, check out PhoneSensorMeasure.SamplingSchemes in the project for which I introduced this base class.