Organizing extension functions

I am working on a convenience Kotlin library that adds a lot of extension functions to classes from a Java library. Currently, I am implementing extension functions in companion objects of multiple classes. I also collect these extension functions in a single kotlin file, so the extension functions can be *-imported. To give you a better idea, here is a dummy example:

The implementation in dummy_impl.kt contains one extension function:

class DummyImpl {
    companion object {
        operator fun Long.plus(s: String) = this + s.toLong()
    }
}

The extension functions is also defined at the top level of dummy so it can be *-imported:

operator fun Long.plus(s: String) = with(DummyImpl) { this@plus + s }

This simply delegates to DummyImpl.

Generally, I like this idea because it gives my project a better structure and users can decide if they want to *-import to use the extension functions, or use a more scoped approach with with(DummyImpl). What I don’t like is the tons of boiler code that is necessary for this. Are there any better approaches to what I am trying to achieve?

Why not just top level functions?

1 Like

I don’t really have a good answer for that. I am pretty much starting from a clean slate so I have all the freedom there is to structure the library in a way I seem fit. These are the reasons why I am thinking about having the implementation logic in separate companion objects:

  1. There will be a lot of extension functions (probably hundreds), and some of them cannot be simple one-liners. Having them as one liners is nice because it is pretty much a list of what is available.
  2. Using extension functions in a scoped way can be useful to use only a subset of them.

But the more I think about it, the more I think that I am probably overthinking and I should just stick with top-level functions in a single file. The benefits of splitting it out into multiple files/companion objects is not worth the added complexity of having nearly identical function definitions in multiple places.

Thad being said: If there was a way to expose by importing (similar to how x becomes available in a Python module when you import x), I would split it up into multiple files, but AFAIK there is no such thing in Kotlin (or Java).

You can create a package (e.g. your.package) that contains a bunch of files with only top level functions, and they’ll be importable with your.package.*.

1 Like

In addition to what have been already said, you could borrow a rule of thumb from the Zen of Python:

Flat is better than nested.

The problem with hierarchies is that they are pretty rigid. What you think is a good code organization at creation time is not necessarily a good organization in use. So I would go with top level functions, too.

2 Likes

Thank you for all your answers. I decided to go with top-level functions. My only concern is compilation time because all code now is in a single file, which will easily exceed 100s of functions, eventually.

Why not split them up into multiple files. You obviously had a way to group them into different objects so why not use the same grouping, but instead of creating different objects put the functions into different files.

I would like for users to be able to just do

import my.package.*

Going with the Python analogy, similar to how it is possible to simply

import numpy as np

and then have access to an abundance of functionality. In my opinion, this is what makes numpy (and the Python ecosystem) so appealing. This is easier to do in Python, because names imported in modules/packages are available in these packages, e.g. a file my_package/__init__.py

from .submodule_a import a
from .submodule_b import b

exposes a and b from modules submodule_a and submodule_b in package my_package.

I don’t think this is possible in Kotlin but, if it was, that would be the perfect solution for me.

To give some context: I am working on Kotlin extension functions for the Java image processing library ImgLib2 so people can use it in Kotlin with operator overloading and all the other cool language features that Kotlin offers.

You can still use multiple files and only need one * import, since the name of the file isn’t used in the import.

my/pkg/A.kt:

package my.pkg

fun a() {
}

my/pkg/B.kt:

package my.pkg

fun b() {
}

Main.kt:

import my.pkg.*

fun main() {
    a()
    b()
}
2 Likes

This is perfect, thank you!

Does this forum allow to mark an answer as the solution to the initial post?

1 Like