1. Introduction
The Kotlin underscore operator is a unique tool for the language, and it has several different uses. Traditional usage centers around unused variables in lambdas or variable destructuring. However, a new feature of Kotlin 1.7 that we should be aware of is the underscore operator for type arguments. Specifically, now the underscore operator can trigger automatic type inference for generic types.
In this tutorial, we’ll delve into the underscore operator in Kotlin for type inference and explore how it can simplify our code and make it easier to read.
2. Kotlin Type Arguments
Type arguments specify the actual types for generic types or functions. Generics provide a way to create classes, interfaces, and functions that can operate on different types without sacrificing type safety.
When defining a generic class, we can use type parameters to represent the types that will be specified later:
class Box<T>(val content: T)
val stringBox: Box<String> = Box("Hello, Kotlin!")
val intBox: Box<Int> = Box(42)
In this example, T is a type parameter for our Box class, and String and Int are type arguments.
3. Understanding the Underscore Operator
Before Kotlin 1.7, the underscore operator could only be used as a placeholder in lambda functions or variable destructuring to indicate unused variables.
3.1. Unused Lambda Parameter with Underscore
When declaring a lambda function with a variable we don’t need, we can replace it with an underscore:
fun main(args: Array<String>) {
val list = listOf("hi")
val list2 = list.mapIndexed { _, item -> item }
println(list2)
}
In this example, the mapIndexed() function requires two parameters: index and item. However, we are only interested in the item, so we use the underscore as a placeholder to explicitly indicate that we’re ignoring the index parameter.
3.2. Variable Destructuring with Underscore
Similarly, when destructuring variables, if we want to ignore the first variable and only extract the second one, we can replace the first variable with an underscore:
fun main(args: Array<String>) {
val (_, second) = "ignored" to "hello"
println(second)
}
In this example, we’re using variable destructuring with a Pair. The underscore is used to ignore the first value in the Pair. This syntax specifically allows us to extract only the values we are interested in and discard the ones we don’t need.
4. Type Inference Assistance
In Kotlin 1.7, the underscore operator introduced a convenient way to replace type declarations when defining generic types. Therefore, this operator allows us to intentionally omit the type parameter and make the compiler infer the types.
4.1. Declaring Generic Values
Before Kotlin 1.7, type arguments were required. If we typed a generic class in our code, we had to specify a valid type parameter:
// Before Kotlin 1.7
class Box<T>(value: T) {
// Some class implementation
}
fun main() {
val intBox: Box<Int> = Box(42)
val anyBox: Box<Any> = Box("Some value")
In our next section, we’re going to see how we can use the underscore operator in our Kotlin code.
4.2. Type Declarations with Underscore
The underscore operator in Kotlin assists with type inference by allowing the compiler to deduce types based on the surrounding context. It’s not about omitting type information but rather signals to the compiler to infer the type when there is sufficient surrounding information.
Let’s see an example within the context of a generic function:
inline fun <T> printElementInfo(element: T) {
println("Element: $element")
}
fun main() {
printElementInfo<_>(42)
printElementInfo<_>("Hello")
}
In this example, the printElementInfo() function is a higher-order function that prints the value of the given element. The underscore operator allows the compiler to infer the type parameter when calling the function.
5. Conclusion
The underscore operator for type inference in Kotlin 1.7 is a valuable addition, contributing to more concise and expressive code. It empowers developers to write cleaner and more maintainable code by allowing the compiler to handle type inference. This specifically works in scenarios where the type can be deduced from the surrounding context, acting as a shorthand when the type parameter is obvious.
As always, the full implementation of these examples is available over on GitHub.