Kotlin is `fun` - Some cool stuff about Kotlin functions

Kotlin is `fun` - Some cool stuff about Kotlin functions


About the Series

Let's deep dive into Kotlin functions, from the simple theory to the more advanced concepts of extension functions and lambda receivers. After reading this series, constructs such as the following should not scare you off anymore:

fun <T, R, S> x(a: (T) -> R, b: R.() -> S): (T) -> S =
    { t: T -> a(t).b() }

The only thing you need is a basic knowledge of Kotlin - I won't explain the 101. Let's get started!


You got me, I am a big fan of Kotlin. Some of the (many) things I love about it are its functional programming support, its compact syntax, and the advanced yet very handy concepts of extension functions, delegates, and more.

Since I won't be able to cover everything offered by the language, let's restrict this (first?) Series to Kotlin functions, starting with some cool stuff about them:


A quick reminder (you can skip it)

For those catching up, a function in Kotlin is declared using the fun parameter, a function name, optional arguments in parenthesis written <name>: <type>, followed by a : and an optional return type.

For example:


fun printHello() {
  println("Hello, World!")
}

printHello() // => writes the string to stdout

Or:

fun ternary(ifTrue: String, ifFalse: String, test: Boolean): String {
    return if (test) ifTrue else ifFalse
}

ternary("yes", "no", 2 == 1) // => "yes"

Functions can, of course, have modifiers (internal, private, inline, etc), but no static! "Static" methods are methods declared in object.


Optional named arguments

Kotlin supports both optional (with defaults) and named arguments, which removes most of the need for the ugly function overloading pattern (java).

Take this function:

fun reformat(
    str: String,
    normalize: Boolean = true,
    useTitleCase: Boolean = true,
    wordSeparator: Char = ' ',
) { /*...*/ }

It declares one required argument of type String, and multiple optional configuration parameters. Note that optional arguments always come after required ones.

All the following calls are perfectly valid:

// ↓↓ preferred
reformat("use defaults")
reformat("change useTitleCase only", useTitleCase = false)

// ↓↓ ugly, but works!
reformat(str = "use defaults")
reformat(useTitleCase = false, str = "changing order")
reformat("all params no names", false, true, '-')
reformat(
    "mix",
    false,
    useTitleCase = false,
    '_' // if all params are present, we can omit the name
)

So optional named arguments let the caller pick which parameters he wants to specify, leaving the rest to the default implementation. While a lot of syntaxes are allowed, I would advise you to:

  • use the same order in the call as in the declaration

  • always use named arguments for optional parameters

  • prefer named arguments when there are many parameters or multiple parameters of the same type.

This works the same way for constructors, which are also functions!

data class Person(name: String, hasPet: Boolean = false)

Person("Georges")
Person("Wallace", hasPet = true)

Single expression functions

When a function's body is only a single expression, one can omit the curly braces and the return by using =.

Hence, this:

fun mod2(x: Int): Int {
    return x % 2
}

Can be turned into an elegant single-line declaration:

fun mod2(x: Int): Int = x % 2

Kotlin type inference is very good, so in this case, you can even omit the return type (though it shouldn't be taken as a good practice):

fun mod2(x: Int) = x % 2

Remember, if/else and try/catch are expressions too! So this also is valid:

fun trySomething() = try { 
    /*...*/ 
  } catch { 
    /*...*/ 
  }

fun conditional() = if (something) a() else b()

I usually abuse this feature, as chaining lets you write many (any ?) complex logics into a single statement in Kotlin. Here is one example from goodreads-metadata-fetcher, where I extract a specific text from an HTML document and convert it to int:

fun getNumberOfPages(doc: Document): Int? =
    doc.getElementById("details")
        ?.getElementsByAttribute("itemprop")
        ?.find { it.attr("itemprop") == "numberOfPages" }
        ?.text()?.split(" ")?.first()
        ?.toInt()

In case you are wondering, the ?. is the safe call operator.

Local functions

Functions can be defined inside other functions. In this case, the inner function is only available from inside the outer function and can access the local variables of the outer scope. This is called a closure.

Here is a good example of a local function:

fun fuzzyCompare(expected: String, actual: String): Boolean {
    fun clean(s: String) = s
        .lowercase()
        .replace("[^a-z0-9]".toRegex(), "")
        .replace("\\s+".toRegex(), " ")
        .trim()

    return clean(actual) == clean(expected)
}

The clean function here is very specific to the fuzzy compare functionality. Instead of making it a private utility function in the same file, we can directly encapsulate it inside the fuzzy compare function itself, making the code both clear and clean.

Even better, Kotlin offers mutable closures! Thus, contrary to Java, it is possible to mutate an outer variable from inside a local function (or a lambda). Here is an example:

fun sumList(list: List<Int>): List<Int> {
    var sum = 0
    fun add(i: Int): Int { 
      sum += i; return sum 
    }
    return list.map { add(it) }
}

println(sumList(listOf(1, 2, 3))) // [1,3,6]

Note: this is a very bad example, as the same can be achieved using functional programming built-ins:

list.runningFold(0, Int::plus).drop(1)

If you are interested in how local functions are implemented under the hood (or their cost), see Idiomatic Kotlin: Local functions from Tompee Balauag.


That's it for this first article, which only scraped the surface. The next one will be more theoretical, giving you the conceptual overview necessary to truly understand Kotlin functions, higher-order functions, and lambdas. I will try to keep it light and fun though, I promise. Stay tuned!