🚀 · Motivation

7 min read · Updated on by

Read on to learn about the problems that extension functions solve, why code isn’t written for computers anymore, and what that means for how we write code.

Extension functions are probably among the features of Kotlin which can be a little more difficult to grasp, for one simple reason — they don’t actually make writing any kind of code that much easier, apart from a small amount of sugar coating. They add form, not function. Because of this, it is often not clear when they should be used or why, and if you Google “Kotlin when to use extension functions”, you will find hundreds of articles addressing this very issue.

There is nothing inherently complicated about the mechanics of extension functions and their syntax can be explained quickly and easily. What really requires discussion is the ‘why’ and ‘how’, which is what I will spend most of these articles talking about.

Importance of maintainability

A well understood but key fact one needs to accept before understanding the benefits of extension functions is this: most code is not written primarily for computers anymore. Code is written for other humans to read, understand, debug, refactor, improve, etc. On a long enough time scale, maintenance outgrows all other costs.

Because of this, our goals when writing code must be different from what they used to be. It is no longer just about writing code that works, but also about writing code that is clear, structured, readable and self-documenting — all things which make maintenance easier. These traits are often valued above all else, including above code which is more optimized, especially under the assumptions discussed in the article on maintainability.

One of the things we do to achieve this is to structure our code in a way that communicates how we are thinking. And when viewed from this perspective, the question regarding extension functions becomes not only what functionality becomes easier to implement with extension functions but also what information becomes easier to communicate to the reader with extension functions.

Limitations of Java

One of the main things we communicate through our code structure is the relationship between things (classes) and behaviors (methods). Java methods have special syntax rules that are able to communicate that a behavior belongs to a certain thing:

  • they separate the thing, the “main” parameter (called a receiver) from the others, and define a very special way it is passed to the method (<importantParameter>.<method>(<otherParameters>)) and accessed inside it (this)
  • they live in the same file, between the { and } defining the class body

Modeling solutions as a set of things (classes) that have behaviors (methods) is a very useful way of structuring our code, because it reflects the way we naturally think about the world. This is, of course, a core motivation for OOP.

However, the Java class model limits the way methods can be defined:

  • A behavior (method) can only belong to a single class
  • All methods which are part of a class must be specified when that class is defined. There is no way to add a method to a class without modifying its definition

This is problematic for one crucial reason: most behaviors are not actually part of the core essence of a class, but are only relevant in certain contexts. Additionally, some behaviors belong equally to two (or more) classes.

Example

Imagine we are writing an IDE for databases. First, we created the core engine, and in the process created a DatabaseTable class:


interface DbHandle {
    fun execute(query: String)
}

interface QueryBuilder

//sampleStart
class DatabaseTable(val dbHandle: DbHandle) {
    fun executeFetch(query: QueryBuilder) = dbHandle.execute(query.toString())
}
//sampleEnd
fun main() {
    val poem = """
        Kotlin, the philosopher in code's book,
        With extension functions, it writes the hook.
        From chapters to verses, a narrative so true,
        In the world of programming, it's the cue!
    """.trimIndent()
    println(poem)
}

Next, we are tasked with writing the GUI module, which renders a visual representation of a DatabaseTable to the screen. It is clear that a renderTo(buffer: FrameBuffer) function is strongly related to a DatabaseTable and could easily be considered its behavior in the context of a GUI application - things are able to render themselves.

At the same time, we might feel some trepidation at the thought of adding the renderTo function to the DatabaseTable class, because it's not actually tied to the core essence of a DatabaseTable. If we were writing a general database library, we most certainly would not include a renderTo function in the DatabaseTable class, would we? The renderTo function exists as a behavior of a DatabaseTable only in the context of a GUI application. In fact, we could go one step further and say it's only relevant in the context of the GUI module - that's the only place we actually talk about rendering. Anywhere else, e.g. in a module that deals with persisting a DatabaseTable to disk, rendering is an alien concept that has no meaning.

Unfortunately, due to the limitations mentioned above, we only have two choices:

Defining the renderTo function outside the class

This has the benefit of allowing us to specify that rendering is a domain in its own right (e.g. by creating a Renderer class).

Unfortunately, it also forces us to write less elegant code when actually using the function, and makes the whole solution more complex by introducing a new concept (a thing that renders), which would not strictly be necessary otherwise.

Another problem is that we have no easy way of writing code that works for “anything that is ‘renderable’” — normally, we would define a Renderable interface, but that would require defining the renderTo function on the class.

Solving this problem is not necessarily impossible, but at the very least becomes more complex.

Defining the renderTo function inside the class

In the long term, this approach leads to bloated classes that are extremely cluttered and difficult to understand. This is because we understand things through the way they behave (what they do), but in this approach, each class contains lots of behavior that has nothing to do with what it actually is at its core. In a sense, we again lose the ability to communicate to a future reader what we were thinking. Additionally, we also lose the ability to communicate that rendering in fact also belongs to a domain of its own.

The situation is even worse when we aren’t the authors of the class in question (i.e. DatabaseTable is part of a third-party library). In this case, we are completely limited to the API defined in the library and only have the option of defining additional methods outside the class. Since the vast majority of classes we work with are part of external libraries, this is the case that happens most often.

Defining Methods Outside of Classes

A lot of these problems would be easier to avoid if we could somehow define methods which belong to a class, but are defined outside of classes. If we could say “this behavior belongs to that class”, but say so in a different file, we would be able to:

  • preserve the special syntax (receivers and this) provided by methods
  • emphasize that a behavior “belongs” to a certain thing
  • emphasize that a behavior is only relevant in a certain context (i.e. it would not be defined as part of the class body, but instead live somewhere else — a top-level definition in another file, or perhaps as a part of another class)
  • keep the core behavior of classes slimmed down to what is actually part of their essence, making them easier to understand (only the core things are defined as methods inside the body)

Extension methods provide a way to do precisely this:


interface DatabaseTable
interface FrameBuffer

//sampleStart
// Inside GUI module
fun DatabaseTable.renderTo(buffer: FrameBuffer) {
  // Same as if you were writing the body of an actual method,
  // but you can only access public members
}

fun doStuff(dbTable: DatabaseTable, screen: FrameBuffer) {
    // ...
    // This works
    dbTable.renderTo(screen)
}
//sampleEnd
fun main() {
    val poem = """
        When you're in the code's gallery so vast,
        Kotlin's syntax is the art unsurpassed.
        With strokes and colors, a masterpiece true,
        In the coding exhibition, it's the view!
    """.trimIndent()
    println(poem)
}

At the beginning of this article, we asked what kind of information becomes easier to communicate with extension functions.

The answer, as we have seen, is:

  • it is easier to communicate what constitutes the essence of a class, and what is just additional behavior relevant only to certain contexts/domains, because we can separate the two (through syntax and through different locations of the definitions). In turn, it is easier to understand what classes are, because they are slimmer and contain less behavior for the reader to absorb
  • it is easier to group behaviors of classes by the contexts/domains they are relevant to, because they needn’t be defined in the same location as the class. This makes it easier to structure our projects.

Leave a Comment

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

The Kotlin Primer