Create your own generic functional builders

Kotlin Builder Inference Explained

Michal Ankiersztajn
3 min readJun 19, 2024

Almost always, when working in a very generic environment, you’ll need to use some type of Builder , luckily Kotlin supports such Builders through inference.

What is Inference?

Inference — it’s how the compiler gets to know the type through logic. Let’s say you want to build a List of type String . You’re able to do it like this:

fun main() {
// If you turn on local variable type hints
// you'll see that list is of type List<String>
val list = buildList {
add("text")
}
}

The compiler knows it’s type String because it infers based on the items that are added to the List .

It’s even smart enough that if you make some mistake, it’ll change the type accordingly. Let’s say you added an Int by mistake:

fun main() {
// Now list is of type List<Comparable<*> & Serializable>
val list = buildList {
add("text")
add(5)
}
}

It’s able to find all subtypes and change the type to the most common subtype (in our case, it’s a Comparable<*> & Serializable). If you were to add a custom class that doesn’t implement anything, it would turn into Any :

fun main() {
// Now list is of type List<Any>
val list = buildList {
add("text")
add(5)
add(Person("name"))
}
}

That happens because every class in Kotlin implement Any by default (more on that here). In short, it’s a class at the top of the hierarchy.

“Build” your own Builder

Let’s say you need to store some data, and you want to provide a very generic access to it. It’ll be best to write a Builder that uses inference in that case. Let’s say you want your List to always get randomly reordered on item add. For that, you’ll build RandomList<T> :

class RandomList<T> {
private val list = mutableListOf<T>()

val items get() = list.toList()

fun add(item: T) {
list.add(item)
list.shuffle()
}
}

Now, we need a builder for our RandomList<T> :

inline fun <T> buildRandomList(builder: RandomList<T>.() -> Unit) =
RandomList<T>().apply(builder)

First of all, your Builder should be a function, an inline function (more on that here), in short, inline reduces the amount of allocations needed to handle lambdas.

Then all it does is apply in a conventional way all the instructions given to it. Here’s how you can use it:

fun main() {
// RandomList<String>
val list = buildRandomList {
add("String")
}
}

You’re now able to build Builders using Inference in Kotlin! And use it better for standard library types :)

In the real system, it’s best to just implement List interface, but it serves well for this simple example.

Thanks for reading! Please follow me if you’ve learned something new!

Based on:

--

--

Michal Ankiersztajn

Android/Kotlin Developer & Applied Computer Science Engineer