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:
- 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
- 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.