- Published on
Kotlin Extension Functions on Specific Generic Types
- Authors
- Name
- Yair Mark
- @yairmark
Today while completing a ticket I came across some code I was itching to clean up. This code had a fortune of repetition and it felt like it had plenty of low hanging fruit I could sort out. For example one thing this code did was call a service that returns a Map
. This maps values were then used in a number of different places but in the same boiler plate, less readable way. For example:
val namesToResults = myAwesomeNameService.doYourThing()
...
val result = namesToResults[someName]?.value.joinToString(",")
?: throw SomeException("We do not have $someName in our records")
...
val result2 = namesToResults[someName]?.value.joinToString(";")
?: throw SomeException("We do not have $someName in our records")
...
//this sure feels like deja vu
val result3 = namesToResults[someName]?.value.joinToString(",")
?: throw SomeException("We do not have $someName in our records")
This is a bit of a contrived example but gives a good feel for the sort of problem I was trying to address. The Java way of doing this would be to make a method for the repeated block and call that instead of the repeated code:
fun concatResults(nameToResults: Map<String,List<String>,someName: String,separator: String): String {
return namesToResults[someName]?.value.joinToString(",")
?: throw SomeException("We do not have $someName in our records")
}
But we can do one better than this in Kotlin using extension functions:
fun Map<String,List<String>.concatResults(someName: String,separator: String = ","): String {
return this[someName]?.value.joinToString(separator)
?: throw SomeException("We do not have $someName in our records")
}
Our original code would then become:
val namesToResults = myAwesomeNameService.doYourThing()
...
val result = namesToResults.concatResults(someName)
...
val result2 = namesToResults.concatResults(someName, ";")
...
//this sure feels like deja vu
val result3 = namesToResults.concatResults(someName)
What is interesting about using extension functions like this is you can create extension functions on a specific collection type. For example:
fun Map<String, Int>.sumValues() = this.values.sum()
fun Map<String, String>.concatValuesUsing(separator: String = ",") = this.values.joinToString(separator)
...
val wordsToNumbers = mapOf("One" to 1, "Two" to 2)
val namesToSurnames = mapOf("John" to "Smith", "Jane" to "Doe")
println(wordsToNumbers.sumValues())
println(namesToSurnames.concatValuesUsing())
println(namesToSurnames.concatValuesUsing("=>"))
//3
//Smith,Doe
//Smith=>Doe
In the above I can use sumValues
on wordsToNumbers
but if I try use concatValuesUsing
on wordsToNumbers
I get a compile error. Using extension functions in this way we can make code far more readable by targeting specific types of collections.