Fun with types, extensions and generics in Kotlin

How we can use Kotlin's features to create a type safe and elegant unit conversion API

Finally Kotlin is an offically supported language by Google for Android development and now many more people can use it to code their Android apps. If this is the first time you are hearing about Kotlin you should start by reading the introduction here https://kotlinlang.org/docs/reference/. To get you excited though I thought I would show off some cool uses of Kotlin's features and how we can use them to create a type safe and elegant API for unit conversion.

Our aim is to create an easy to use API that can convert between different units within a category easily, importantly it should also not let you try to convert a meter to hours. It would be nice if we didn't even need to worry about converting but could just write 1 hour plus 6 minutes.

Lets get started

First we will make a class to store our unit definitions. Each unit shall have a name and a ratio compared to a base unit of our choosing.

open class Unit(val name: String, val ratio: Double) {

}

We have made the class open because it will be useful for other developers to build their own units on top of our class definitions. Making it open allows them to subclass this super class.

This unit class will work by providing functions to convert between the base unit and the actual unit. The base unit is just a way to store the value and convert between different units. For example if we were doing distance conversions we could choose the base unit to be meters. It doesn't matter what we choose the base unit to be, we just need to input the ratio between the base and the unit when creating the definition.

Lets make a unit class to see what I mean.

class Distance(name: String, ratio: Double) : Unit(name, ratio) {
    companion object Factory {
        val Mile = Distance("Mile", 1609.344)
        val Kilometer = Distance("Kilometer", 1000.0)
        val Meter = Distance("Meter", 1.0)
        val Centimeter = Distance("Centimeter", 0.01)
        val Millimeter = Distance("Millimeter", 0.001)
    }
}

We have made a Distance unit and added some definitions of the different members of Distance using a companion object. There are 1609.344 meters in a mile so that is what we use for the ratio. The companion object is roughly like static members in Java. It allows you to access these static members with Distance.Mile and Distance.Meter.

Now we have defined our unit specification but we still don't have any way to create an amount of the unit. We need another class to store amount and what type of unit the amount is. Lets create a class called Quantity to do just this.

open class Quantity(val amount: Double, val unit: Unit) {
}

Our quantity class now allows us to create an object for say 10 meters.

We have a good foundation now that we can work off. We still don't have any functionality though. Lets add a way to convert between the different members of our unit. We will add two functions to our Unit class to handle converting from and converting to the base unit.

open class Unit(val name: String, val ratio: Double) {
    fun convertToBaseUnit(amount: Double) = amount * ratio
    fun convertFromBaseUnit(amount: Double) = amount / ratio
}

We will also add a function to convert between different quantities of units.

open class Quantity(val amount: Double, val unit: Unit) {
    fun to(unit: Unit): Quantity {
        val baseUnit = this.unit.convertToBaseUnit(amount)
        return Quantity(unit.convertFromBaseUnit(baseUnit), unit)
    }
}

Now we can write the following code to convert between units!

val tenMiles = Quantity(10.0, Distance.Mile)
val kilometers = tenMiles.to(Distance.Kilometer).amount

There is a problem though, lets say we also had a time unit we currently could create garbage information by passing in a time unit to our conversion function.

val rubbish = tenMiles.to(Time.Second).amount

This wouldn't crash, create a warning or even fail to compile. This is not good, a driving principal of languages like Kotlin is safety. We should bring errors like this to the developers attention at compile time. Lucky for us, this is easy to fix. We can use the power of generics to ensure we only convert between the same type of unit. Lets modify our Quantity class to be generic.

open class Quantity<T: Unit>(val amount: Double, val unit: T) {
    fun to(unit: T): Quantity<T> {
        val baseUnit = this.unit.convertToBaseUnit(amount)
        return Quantity(unit.convertFromBaseUnit(baseUnit), unit)
    }
}

Now passing in a time unit like above will lead to a compile time error! Perfect. We now have a minimal but functional distance conversion library that could be extended to many other types of units with ease.

Extensions

We can do better. We haven't really done anything yet that couldn't have been done in Java.

Lets start by making our Quantity API a little easier to use, currently we can convert between units but to do so we have to call our to(unit: Unit) method. Wouldn't it be nicer if we could do something like tenMiles.kilometers.amount? In Java this would not have been possible as we have no way to just add a method to just the Distance versions of our Quantity class. In Kotlin it is trivial.

val Quantity<Distance>.miles get() = this.to(Distance.Mile)
val Quantity<Distance>.kilometers get() = this.to(Distance.Kilometer)
val Quantity<Distance>.meters get() = this.to(Distance.Meter)
val Quantity<Distance>.centimeters get() = this.to(Distance.Centimeter)
val Quantity<Distance>.millimeters get() = this.to(Distance.Millimeter)

We can use Kotlin's extension properties feature to do just what we want. We extend Quantity to add properties for each of our distance types. Our conversion API is much cleaner now!

Now we can convert between units with ease but it is still a bit verbose to create the initial quantity. We need to go deeper!

Currently to create our quantity we use

val tenMiles = Quantity(10.0, Distance.Mile)

But wouldn't it be easier just to use

val tenMiles = 10.miles

This might seem crazy if you are new to Kotlin but we can pretty much extend everything! That includes the Number type that all number primitives in Kotlin are subclasses of.

We just have to add the following code:

val Number.meters: Quantity<Distance> get() = Quantity(this.toDouble(), Distance.Meter)
val Number.kilometers: Quantity<Distance> get() = Quantity(this.toDouble(), Distance.Kilometer)
val Number.miles: Quantity<Distance> get() = Quantity(this.toDouble(), Distance.Mile)
val Number.centimeter: Quantity<Distance> get() = Quantity(this.toDouble(), Distance.Centimeter)
val Number.millimeter: Quantity<Distance> get() = Quantity(this.toDouble(), Distance.Millimeter)

Now we have extended the Number type with a new property for each of our distance units we can write code like 2.kilometers!

Operator Overloading

We're really cooking now but we can do even better, now that we have extended the Number class it would be great to be able to create simple mathematical expressions with our unit type. Something like

val totalDistance = 10.kilometers + 500.meters

Of course, using Kotlin's operator overloading we can achieve this too. To override an operator you simply add another function for each operator you want to overload to your class. Adding this code to our Quantity class acheives our goal.

operator fun plus(quantity: Quantity<T>): Quantity<T> {
    val converted = quantity.to(this.unit).amount
    val amount = this.amount + converted
    return Quantity(amount, this.unit)
}

operator fun minus(quantity: Quantity<T>): Quantity<T> {
    val converted = quantity.to(this.unit).amount
    val amount = this.amount - converted
    return Quantity(amount, this.unit)
}

//Note: It doesn't really make much sense to do this as the unit would change, but its here
//just to show how to override multiply and divide
operator fun times(quantity: Quantity<T>): Quantity<T> {
    val converted = quantity.to(this.unit).amount
    val amount = this.amount * converted
    return Quantity(amount, this.unit)
}

operator fun div(quantity: Quantity<T>): Quantity<T> {
    val converted = quantity.to(this.unit).amount
    val amount = this.amount / converted
    return Quantity(amount, this.unit)
}

Now we really have reached our target, a type safe and elegant unit conversion API that demonstrates some of the great features Kotlin brings to the Android/Java development scene. Our API could easily be extended to add many more unit types and also more sub units. The complete code including a sample Time class is available in this gist https://gist.github.com/bentrengrove/9759a3fbb564d62e1e63f417c58a3895.

What do you think of Kotlin? If you have a good example I would love to hear about it!

UPDATE: Will Richardson pointed out that it doesn't really make sense to multiply and divide units in my current form as that would actually change the unit. For example, 2 meters * 2 meters is actually 4 meters squared. He has authored a great blog post with a fix! Definitely worth a read! http://javanut.net/2017/05/23/more-fun-with-generics-in-kotlin/