How to structure functional code in Kotlin?

Viewed 651

I'm wondering what is the best way to structure functional code in Kotlin.

I don't want to create unnecessary objects (and put functions in a closed scope) to group my functions with. I heard I can group functions by packages and put them in the top level of a package. I've also seen in Arrow library that functions are grouped in interface companion objects as extension functions and this looks the best except the fact I need to create a companion object.

Object way:

object Container {
    fun myFunc() = ...
}

...

Container.myFunc()

Package way:

package myPackage

fun myFunc() = ...

...

myPackage.myFunc()

Arrow way:

interface Container {
    companion object {
        fun Container.myfunc() = ...
    }
}

...

Container.myFunc()

What is the best way to structure my functions and group them using Kotlin? I want to keep a pure functional style, avoid creating any sort of objects, and be able to easily navigate to functions by namespaces like:

Person.Repository.getById(id: UUID)

1 Answers

If I understand you correctly, you're looking for the concept of namespaces (structured hierarchical scope for accessing symbols).

Kotlin does not support namespaces, but as you found out, there are different ways of having similar functionality:

  1. object declarations. They pretty much fulfill the needs, however they lead to creation of an actual object in JVM and introduce a new type, which you don't need. The Jetbrains team generally discouraged the use of objects as namespaces, but it's of course still an option. I don't see how companion objects inside interfaces add any value. Maybe the idea is to limit the scope to classes which implement the interface.

  2. Top-level functions. While possible, top-level functions in Kotlin pollute the global namespace, and the call-site does not let you specify where they belong. You could of course do workarounds, but all of them are rather ugly:

    • Fully qualify the package com.acme.project.myFunc()
    • Use a deliberately short, but no longer domain-representing package functional.myFunc()
    • Call function without package, but with prefix package_myFunc()
  3. Extension functions. If the functionality is closely related to the objects it's operating on, extension functions are a good option. You see this for the Kotlin standard collections and all their functional algorithms like map(), filter(), fold() etc.

  4. Global variables. This does not add much over the object approach, just prevents you from introducing a named type. The idea is to create an anymous object implementing one or more interfaces (unfortunately, without interfaces the declared functions are not globally accessible):

    interface Functionals {
        fun func(): Int
    }
    
    val globals = object : Functionals {
        override fun func() = 3
    }
    

    It is mainly handy if your object implements different interfaces, so that you can pass only a part of the functionality to different modules. Note that the same can be achieved with objects alone, as they can implement interfaces too.

Related