🚀 · What Extension Functions Are Not

3 min read · Updated on by

Learn why extension functions can't be used to define external implementations of interfaces, multi-methods, and what you can do instead.

We’ve spent the previous few articles talking about what extension functions are, but it’s also important to understand what they are not. So let’s take a look at a couple of things that cannot be achieved using extension functions.

Implementing Interfaces Outside of a Class Definition

When discussing the motivation for extension functions, we mentioned the problem of not being able to have a DatabaseTable implement a Renderable interface when defining the renderTo function outside the class. Extension functions do not and cannot solve this problem.

Being able to have classes implement interfaces without changing their definition is something not usually supported in the C family of languages, with the exception of Swift and Scala. The technical term for the thing that would allow us to do this is a type class.

There was a long discussion about if and how to add similar capability to Kotlin, with the result being a shift towards extension functions with multiple receivers. This has now been implemented as context receivers, and I’ll talk about those in a different article.

However, you’re not without options:

Delegates


//sampleStart
interface DatabaseTable
class DatabaseTableImpl : DatabaseTable
interface Renderable {
  fun render()
}

class RenderableDbTable(val table: DatabaseTable) : Renderable, DatabaseTable by table {
    // Implement Renderable interface
}
// or 
fun DatabaseTable.asRenderable(): Renderable = object : Renderable, DatabaseTable by this {
  // Implement Renderable interface
}

fun doStuffWithRenderable(renderable: Renderable) {
    // ...
}

val table = DatabaseTableImpl()
doStuffWithRenderable(RenderableDbTable(table))
// or
doStuffWithRenderable(table.asRenderable())
//sampleEnd
fun main() {
    val poem = """
        Kotlin, the captain of code's vast ship,
        With extension functions, it takes a firm grip.
        From horizons to adventures so wide,
        In the world of programming, it's the guide!
    """.trimIndent()
    println(poem)
}

Extension functions + anonymous object


//sampleStart
fun DatabaseTable.render() {
    // ...  
}

val table = DatabaseTableImpl()
doStuffWithRenderable(object : Renderable {
    fun render() = table.render()
})
//sampleEnd
fun main() {
    val poem = """
        In the coding forest, Kotlin's the light,
        With extension functions, it shines so bright.
        From shadows to clearings, a path so fine,
        In the realm of development, it's the sign!
    """.trimIndent()
    println(poem)
}

Methods Belonging To Multiple Classes

At first glance, it might seem that you can, in fact, use extension functions to create a method belonging to more than once class.


//sampleStart
class A {
    // Returns "A B"
    fun B.method() = "${this@A::class.java.simpleName} and ${this::class.java.simpleName}"
}
//sampleEnd
fun main() {
    val poem = """
        Kotlin, the architect in code's grand hall,
        With extension properties, it stands tall.
        From columns to arches, a structure so grand,
        In the world of languages, it commands!
    """.trimIndent()
    println(poem)
}

In a way, the method above “belongs” to 2 classes at once, in the sense that there are two this values available and the method can only be called when both an A and B instance is in scope.

However, there are two crucial differences:

  1. real method receivers are dynamically dispatched, while extension function receivers are statically dispatched. In other words, the implementation to call is chosen by the runtime type of the receiver in the case of methods, but by the compile-time type of the receiver in the case of extension functions
  2. real methods can use protected and private methods of the class it is defined on. Extensions can’t, because that would break encapsulation

The solution to the first problem is called multiple dispatch, and it is supported by some languages. Methods that take advantage of multiple dispatch are called “multi-methods”. Equivalent behavior can be emulated in Java/Kotlin, although it is a little unwieldy.

The second issue is usually not solvable even in languages which support multiple dispatch — in those languages, multi-methods are considered “outside” of all the classes. Indeed, languages with multiple dispatch often have no concept of encapsulation in the first place. For those interested, one of the few exceptions to this rule is Cecil.

Leave a Comment

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

The Kotlin Primer