Create your own generic functional builders
Kotlin Builder Inference Explained
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: