🏎️ · Operators as Extensions

3 min read Β· Updated on by

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?

Leave a Comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.

The Kotlin Primer