Type Aliases FTW!
Uzi Landsmann
Systemutvecklare
Kotlin’s type aliases, as you probably already know, are a way to give a class or an expression a new name. Marginally useful, you might think, but not so helpful for your daily programming. Still, perhaps you should consider using them since there are several ways in which they can add value to your code and make your life slightly better. In this article, I’d like to present some type aliases recipes that can make your code shine.
Recipe one: domain-driven design
Suppose you’re using a third-party library that defines the following classes:
data class Account(val id: UUID, val groupId: UUID, val balance: Double)
data class AccountGroup(val id: UUID, val name: String)
data class Person(val id: UUID, val name: String, val accountId: UUID)
Now, suppose someone in your team (not you, obviously, you know better) creates the following repository interface, which includes the following methods:
interface AccountRepository {
fun getPersonAccount(id: UUID): Account
fun getGroupAccountMap(): Map<UUID, List<UUID>>
}
This code is hard to understand because it is difficult to know which IDs are required as parameters and which are included in the return values. For example, does getPersonAccount require the account ID or the person ID as a parameter? And which IDs are listed in the getGroupAccountMap return map?
Since you can’t redefine the fields in the third-party library, you could instead define some type aliases and use them to redesign the interface, to make the code more readable:
typealias AccountId = UUID
typealias AccountGroupId = UUID
typealias PersonId = UUID
typealias GroupIdToAccountIdsMap = Map<AccountGroupId, List<AccountId>>
interface BetterAccountRepository {
fun getPersonAccount(id: PersonId): Account
fun getGroupAccountMap(): GroupIdToAccountIdsMap
}
Recipe two: integration with ease
Suppose you are creating an integration between two systems, a producer and a consumer, and both are using objects with roughly the same names:
object Producer {
data class Assembly(val name: String, val type: String)
data class Component(val name: String, val type: String, val assembly: Assembly)
}
object Consumer {
data class Struct(val type: String)
data class Comp(val type: String)
}
Your converter interface might look like this:
interface Converter {
fun convertStructure(assembly: Assembly): Struct
fun convertComponent(component: Component): Comp
}
Reading this code requires you to remember the names of the different classes on both sides, which makes the code slightly confusing, especially when many different instances and types are involved. To make the interface more intelligible, you can define some type aliases that will harmonize the object names:
typealias ProducerStructure = Producer.Assembly
typealias ProducerComponent = Producer.Component
typealias ConsumerStructure = Consumer.Struct
typealias ConsumerComponent = Consumer.Comp
interface BetterConverter {
fun convertStructure(producerStructure: ProducerStructure): ConsumerStructure
fun convertComponent(producerComponent: ProducerComponent): ConsumerComponent
}
Recipe three: working with Java
Suppose you’re working on a cross-language project where the following Java class is defined:
public class Cat {
private final String name;
private final int age;
private final String gender;
private final String race;
private final int height;
private final String color;
public Cat(String name, int age, String gender, String race, int height, String color) {
this.name = name;
this.age = age;
this.gender = gender;
this.race = race;
this.height = height;
this.color = color;
}
// getters etc
}
Creating an instance of this class in Kotlin is easy, but since there are many parameters, this could get confusing, and it would be very helpful if you could use named arguments like you’re used to in Kotlin. However, when you try it, Intellij gives you this error:
Guess what — you can use a type alias to create a Kotlin alias of the Java class, and use named arguments with that one instead:
(Hint: this is when you’re supposed to yell “OH MY GOD” and scare your co-workers)
Finally, a warning: type aliases are not more type safe than the class or expression they refer to. In my first example, you could use any UUID as an argument to thegetPersonAccount method, even though a PersonId is expected. To do it properly, you should define your classes in a DDD manner instead. Still, I think type aliases can add a lot of clarity to your code when used carefully.
Have fun type aliasing!