1. Overview
Lambda expressions are powerful tools for crafting concise and expressive code in Kotlin. Further, we can use the default implicit parameter it in lambda expressions.
In this tutorial, we’ll explore what it is and how to improve the readability of our lambada expressions.
2. Introduction to the Problem
First, let’s look at an example of using it as the parameter name in lambda expressions:
val result = listOf("Kai", "Liam", "Eric", "Kevin")
.filter { it.length == 4 }
.map { it.uppercase() }
.sortedBy { it }
assertEquals(listOf("ERIC", "LIAM"), result)
When we read the code above, it’s pretty straightforward. We take a list of names as the input. First, we filter the String values whose length is four. Then, the filtered String elements are converted to uppercase and sorted.
We perform each processing with a lambda expression. In this example, lambda expressions with it parameters make the function calls fluent and easy to understand.
Next, let’s see another example. We’ll use the same list of names as the input, but this time, we aim to encode the String values. We’ll achieve this by replacing each character with its ASCII code incremented by one. For instance, ‘K‘ will be transformed into ‘L‘, and ‘a‘ will become ‘b‘ after the encoding process:
val result = listOf("Kai", "Liam", "Eric", "Kevin").map {
it.map {
(it + 1).also { log.debug("Char After: $it") }
}.joinToString(separator = "")
}
assertEquals(listOf("Lbj", "Mjbn", "Fsjd", "Lfwjo"), result)
As the code above shows, we continue to utilize lambda expressions and default implicit it parameters to execute the encoding logic. However, due to nested lambda expressions within the code, what the it parameter indicates may not be immediately apparent. For instance, in the example code, it can mean a String element in the list, a character, and a character after the encoding.
Next, let’s examine the it parameter more closely and explore how to improve the readability of lambda expressions.
3. What Is It?
When a lambda expression takes a single parameter, Kotlin offers the option to omit the explicit parameter declaration and instead refer to it implicitly using it.
We often use the implicit parameter it in built-in functions such as let(), also(), and the functions we’ve seen earlier. But it isn’t limited to the built-in functions.
Next, let’s create an extension function to the Long class as an example:
fun Long.calc(desc: String, operation: (Long) -> Long) = "$desc($this) = ${operation(this)}"
The second parameter (operation) of the calc() extension function is a function. Further, this function has only one Long parameter. Therefore, when we pass a lambda express as the operation parameter, we can use the implicit it parameter:
val negateOutput = 2L.calc("negate") { -it }
assertEquals("negate(2) = -2", negateOutput)
val squareOutput = 2L.calc("square") { it * it }
assertEquals("square(2) = 4", squareOutput)
As we can see, this elegant shorthand simplifies code and enhances readability, especially for short and straightforward operations.
4. Improving the Readability
Now that we understand what it is, let’s explore how to enhance the readability of lambda expressions when it can cause ambiguity.
4.1. Overriding it with an Explicit Parameter Name
In Kotlin, *we can override the implicit it parameter in lambda expressions by explicitly declaring a parameter name in the format “ParamName ->“*. This allows for a more transparent comprehension of the code, particularly in cases where nested lambda expressions might cause confusion.
Let’s take the earlier String encoding example to show how to use explicit parameters:
val result = listOf("Kai", "Liam", "Eric", "Kevin").map { name ->
name.map { originalChar ->
(originalChar + 1).also { resultChar -> log.debug("Char After: $resultChar") }
}.joinToString(separator = "")
}
assertEquals(listOf("Lbj", "Mjbn", "Fsjd", "Lfwjo"), result)
In this example, we’ve explicitly named parameters within lambda expressions. Now, when reading the code, there’s no ambiguity regarding the purpose of the parameter within the lambda expression.
4.2. Using Deconstructing Declarations
We can also use Kotlin’s deconstructing declarations to override the it parameter to enhance readability. Next, let’s see how to achieve it through an example:
val players = listOf("Kai" to 42, "Liam" to 50, "Eric" to 27, "Kevin" to 49)
In the players list, we have four Pair instances. Each Pair holds a player’s name (String) and score (Int). Our goal is to generate a simple report of the players:
val expectedOutput = listOf(
"Kai's score: 42",
"Liam's score: 50",
"Eric's score: 27",
"Kevin's score: 49"
)
We can use it to achieve this:
val output1 = players.map { "${it.first}'s score: ${it.second}" }
assertEquals(expectedOutput, output1)
However, when reading or writing this code, it may be necessary to refer back to the player’s declaration to check what it.first and it.second represent.
Alternatively, we can override it using a deconstructing declaration to make the code easier to understand:
val output2 = players.map { (player, score) -> "$player's score: $score" }
assertEquals(expectedOutput, output2)
5. Conclusion
In this article, we explored Kotlin’s lambda expressions and discovered that implicit it parameters can lead to ambiguity, especially with nested lambda expressions. We then discussed how to enhance code readability by overriding it with explicit parameters.
As always, the complete source code for the examples is available over on GitHub.