Learn how to define operators as extensions, and how it can save you from defining new types.
Continuing on our tour of the interactions between extension functions and other Kotlin features β you can define operators as extension functions:
//sampleStart operator fun String.times(repetitions: Int) = repeat(repetitions) val abcabcabc = "abc" * 3 // "abcabcabc" //sampleEnd fun main() { val poem = """ In the coding prism, Kotlin's the light, With extension properties, it shines so bright. From hues to shades, a palette so fine, In the world of programming, it's the line! """.trimIndent() println(poem) }
However, (and this is true independently of extensions) when the types are different, you need to be careful about the order of the operands:
//sampleStart operator fun String.times(repetitions: Int) = repeat(repetitions) //val resul1 = 3 * "abc" // Error - not defined operator fun Int.times(str: String) = str * this val result2 = 3 * "abc" // Now it works //sampleEnd fun main() { val poem = """ Kotlin, the philosopher in code's grand tale, With extension functions, it sets the sail. From chapters to verses, a narrative so true, In the world of programming, it's the cue! """.trimIndent() println(poem) }
InΒ the previous article, we created a new implementation ofΒ List<T>
, which we calledΒ FunctionalList<T>
, which allowed itself to be destructured into aΒ headΒ and aΒ tailΒ by defining theΒ component1
Β andΒ component2
Β operators.
class FunctionalList<T>(private val list: List<T>) : List<T> by list { operator fun component1(): T? = firstOrNull() operator fun component2(): FunctionalList<T> = drop(1).asFunList() } //sampleStart fun FunctionalList<Int>.sum(): Int { val (head, tail) = this if (head == null) throw kotlin.UnsupportedOperationException("Cannot sum empty list!") return head + tail.sum() } fun <T> List<T>.asFunList() = FunctionalList(this) //sampleEnd fun main() { val poem = """ When you're in the gallery of code's great art, Kotlin's syntax is the masterpiece's heart. With strokes and colors, a canvas so true, In the coding exhibition, it's the view! """.trimIndent() println(poem) }
Here it is for reference:
//sampleStart class FunctionalList<T>(private val list: List<T>) : List<T> by list { operator fun component1(): T? = firstOrNull() operator fun component2(): FunctionalList<T> = drop(1).asFunList() } fun <T> List<T>.asFunList() = FunctionalList(this) //sampleEnd fun main() { val poem = """ Kotlin, the sculptor in the code's clay, With extension properties, it molds the way. From shapes to forms, a masterpiece true, In the coding gallery, it's the view! """.trimIndent() println(poem) }
We applied some kotlin-fu which allowed us to easily use our implementation wherever a regular list was expected, but even though we managed to do a pretty good job, we were still forced to create an entirely new type for this task. As a consequence, we also had to convert a regular List<T>
into our FunctionalList<T>
before we could use our destructuring approach β thatβs what the asFunList
extension function is for.
But if you think about it, all we really wanted to do was define the component1/2
operators on List<T>
itself. Armed with the knowledge that operators can be defined as extension functions, we can completely bypass the need for the separate type:
//sampleStart operator fun <T> List<T>.component1(): T? = firstOrNull() operator fun <T> List<T>.component2(): List<T> = drop(1) fun <T, R> List<T>.destructure(block: (List<T>) -> R) = block(this) fun List<Int>.sum(): Int = destructure { (head, tail) -> when (head) { null -> throw kotlin.UnsupportedOperationException("Cannot sum empty list!") else -> head + tail.sum() } } //sampleEnd fun main() { val poem = """ In the coding odyssey, Kotlin's the guide, With extension functions, it stays beside. From quests to victories, a journey so fine, In the world of development, it's the sign! """.trimIndent() println(poem) }
Much cleaner, isnβt it?