Kotlin: How to Do Higher-Order Functions

In Kotlin gibt es die Möglichkeit ganze Funtionen als Parameter in eine Methode zu übergeben oder zurückzugeben. Klingt verwirrend, ist aber in der Praxis viel verständlicher, weil jede higher order funtion in Kotlin beginnt mit fun!

, Gally Tioman

In diesem Artikel möchte ich einen Einblick zeigen, wie higher-order-functions in der schönen Sprache Kotlin geschrieben werden. Ich habe versucht, meine Codeschnipsel simpel zu halten, dass man sie einfach kopiert und testen kann. Und wenn Sie bereits ein Experte sind, kann mein letztes Beispiel interessant sein 😉

Example 1: Higher Order Function – Starter Pack

Starten wir mit einer super simplen higher-order-function. Die basicHigherOrderFunction Methode bekommt einen Parameter namens myFunction  mit Unit als return value. Man erkennt, dass es sich um eine higher-order-function handelt, wenn man sich den Typ des Parameters genauer anschaut: () -> R . Nun kann ich den übergebenen parameter myFunction wie eine Methode mit normalen Klammern in meiner Methode basicHigherOrderFunction aufrufen.

/** My Higher-Order Function */
fun basicHigherOrderFunction(myFunction: () -> Unit) {
    println("Start my Function!")
    myFunction() // call my received parameter
    println("Function finished")
}

Wenn man nun die Methode basicHigherOrderFunction aufrufen würde, ist es möglich eine ganze Funktion als Parameter mit geschweiften Klammern zu übergeben. Da die Methode basicHigherOrderFunction allerdings nur einen Parameter hat und wir in Kotlin unterwegs sind, kann man die runden Klammern auch entfernen wie im letzten Beispiel.

fun main() {
    basicHigherOrderFunction({ // inside normal brackets
        val result = 1 + 2
    })
    basicHigherOrderFunction { // only curly brackets
        val result = 1 + 2
    }
}

Super schlicht oder? 😎

Example 2: Higher Order Function mit mehreren Parametern

In meinem nächsten Beispiel möchte ich etwas komplizierter werden: Ich möchte meine Funktion nur ausführen, wenn der übergebene Username einem bestimmten String entspricht.

/** My Higher-Order Function */
fun execute(username: String, myFunction: () -> Unit) {
    when(username) {
        "Arnold" -> myFunction()
        else -> throw NotAuthorizedException("User is not authorized")
    }
}

Wenn man sich meine higher-order-function execute(...) anschaut, erkennt man, dass ich jetzt zwei Parameter erwarte. Der String username und mein Lambda myFunction. Auf der Seite, welche die Methode aufruft – in diesem Fall die main Methode – übergebe ich nun den Usernamen und außerhalb der runden Klammern meine Funktion, welche dann aufgerufen werden soll, wenn der username einen bestimmten Wert hat.

In diesem Beispiel wird nun eine NotAuthorizedException geworfen, da mein Username dem Wert Lou und nicht Arnold entspricht. Und mein Lambda wird nie ausgeführt, weswegen wir auch kein println statement sehen.

fun main() {
    execute("Lou") {
        println("Print this if I am allowed")
    }
}

Kann man aber noch komplexer werden? – Das kann man immer 😍

Example 3: Higher Order Function mit eigenem Parameter

Jetzt möchte ich meiner übergebenen Funktion einen Parameter mitgeben, welche ich in meinem Lambda benutzen kann.

/** My Higher-Order Function */
fun getRandomNumber(myFunction: Int.() -> Unit) {
    println("Some really complex calculating...")
    val myRandomNumber = Random.nextInt()

    println("Passing result to myFunction()")
    myFunction(myRandomNumber)
}

Man erkennt, dass sich der Lambda Typ minimal verändert hat. Und zwar von  () -> Unit zu Int.() -> Unit. Der Int vor dem . sagt Kotlin, dass myFunction einen parameter vom type Int erwartet. Also muss ich in meiner Methode getRandomNumber meinem Lambda Parameter einen Integer übergeben. In diesem Fall einen zufälligen Int. In meiner  main Funktion werde ich diesen nur printen. Zugriff auf den Integer bekomme ich über das Keyword this.

fun main() {
    getRandomNumber {
        println("This is my random number: $this")
    }
}

Und ja, es geht noch komplexer und verrückter! 🤓

Example 4: Higher Order Function mit return lambda

Nun dreh ich den Spieß um. Ich will einen zufälligen Integer in meiner main Methode meinem Lambda übergeben, um diesen dann in meiner printInt Methode diesen Wert zu printen.

/** My Higher-Order Function */
fun printInt(myFunction: () -> Int) {
    println("Passing result to myFunction()")
    val myRandomNumber = myFunction()
    println("This is my random Number $myRandomNumber")
}

Man erkennt jetzt wieder, dass sich die Deklaration des myFunction Parameters von Int.() -> Unit zu () -> Int verändert hat. Das bedeutet, dass mein übergebener Lambda mit dem Namen myFunction jetzt einen Integer statt eines Units zurückgibt, welchen ich dann in meiner printInt Methode benutzen kann.

fun main() {
    printInt {
        println("Some really complex calculating...")
        Random.nextInt()
    }
}

Allerdings gibt es noch ein paar Keywörter, welche man sich umbedingt anschauen sollte 😈

Inline Modifier

Wenn man eine super einfache higher-order-funktion wie in meinem ersten Beispiel hat, erstellt Kotlin jedes mal ein eigenes Lambda Object im Hintergrund für den Part in den geschweiften Klammern. Wenn man jetzt eine higher-order-function in einer while-loop aufrufen würde, führt das dazu, dass ziemlich viele Objekte im Hintergrund erstellt werden.

Natürlich hat Kotlin hier auch eine Lösung parat 🤘 Und zwar das inline keyword, welche man an den Anfang der Funktion packt. Aber was verändert das jetzt?

Mithilfe des inline keyword kopiert der Kotlin Compiler den Inhalt der higher-order-function auf die Seite des Aufrufers. So werden also gar keine neuen Objekte im Hintergrund erstellt.

Wie genau sieht das jetzt aber dann aus? Hier ein Beispiel. Kotlin erstellt ein eigenes Objekt nur für val result = 1 + 2 . In diesem Fall hätte ich jetzt zwei unnötige Objekte im Hintergrund 😱

fun main() {
    basicHigherOrderFunction({ // inside normal brackets
        val result = 1 + 2
    })
    basicHigherOrderFunction { // only curly brackets
        val result = 1 + 2
    }
}

Weil niemand unnötige Objekte benötigt schreibe ich das inline keyword an den Anfang der basicHigherOrderFunction. Der Kotlin Compiler macht nun so etwas:

fun main() {
    println("Start my Function!")
    val result = 1 + 2
    println("Function finished")
    println("Start my Function!")
    val result1 = 1 + 2
    println("Function finished")
}

Keine unnötigen Objekte überhaupt! Man kann sehen, dass der Inhalt meiner higher-order-function auf die caller seite gepackt wurde.

Um jetzt den Leser dieses Artikels vollends zu verwirren möchte ich auch noch auf einen interessantes Anwendungsfall in Kotlin eingehen, welcher vor allem für die Experten interessant sein könnte.

Generics und eine where clause in der Methode Deklaration?

Wenn man Code in Kotlin schreibt hat man mit hoher Wahrscheinlichkeit schonmal die  .ifEmpty { ... } Extension-Function benutzt, welche dem Entwickler erlaubt eine Art default wert zurückzugeben, wenn eine collection leer ist. Wenn man sich diese Extension function anschaut erkennt man, dass es eine higher-order-function ist, welche auch den inline Modifier benutzt. Aber was genau passiert da? 😈

public inline fun <C, R> C.ifEmpty(defaultValue: () -> R): R where C : Collection<*>, C : R {
    return if (isEmpty()) defaultValue() else this
}

➡️  Mithilfe des inline modifiers erkennen wir, dass der Inhalt der Methode ifEmpty an die Stelle kopiert wird, wo man diese Methode aufruft, welche die Erstellung eines Object verhindert.

➡️  Danach wurden zwei Generics C und R definiert

➡️  Da es sich um eine Extension Function handelt erkennt man nun, dass die Methode das Generic C erweitert.

➡️  Als Parameter wurde ein Lambda mit dem Namen defaultValue übergeben, welche ein return value vom typ  R hat und die ganze Methode ifEmpty selbst gibt einen wert vom Typ R zurück. Wenn man allerdings diese Methode auf einer Collection aufruft, die nicht leer ist, wird immer noch eine Collection zurückgegeben. Aber wie kann as möglich sein?

➡️  Und hier kommt der interessante Teil. In Kotlin kann man eine where clause nach der deklaration des return values definieren. Hier hat das Kotlin Team definiert, dass  C eine Collection sein muss und C auch ein Typ R ist. Das ist der Trick!

Falls du noch mehr darüber lesen willst klicke hier.

Wenn du diesen Teil meines Artikels erreicht hast, hoffe ich, dass etwas interessantes für dich dabei war.

Danke fürs Lesen 🤓

Allgemeine Anfrage

Wir freuen uns darauf, Ihre Herausforderungen zusammen in Angriff zu nehmen und über passende Lösungsansätze zu sprechen. Kontaktieren Sie uns – und erhalten Sie maßgeschneiderte Lösungen für Ihr Unternehmen. Wir freuen uns auf Ihre Kontaktanfrage!

Jetzt Kontakt aufnehmen