Wiemy już, że w Kotlinie są funkcje i możemy wywnioskować, że są bytami podstawowymi. W języku ogólno-informatycznym mówimy też o funkcjach wyższego rzędu (ang High-order functionW (HOF). Kotlin ogarnia te oba pojęcia (są one bliskoznaczne).

Funkcje wyższego rzędu – co to?

Zacznijmy od maleńkiej teorii. Funkcją wyższego rzędu nazywamy taką funkcję, która jako parametr przyjmuje inną funkcję lub wynikiem jej działania jest funkcja. Z funkcjami wyższego rzędu nierozerwalnie są związane dwa kolejne pojęcia.

Currying

Jest to operacja polegająca na zamianie funkcji o dwóch argumentach zwracających pewną wartość na funkcję o jednym argumencie zwracającym funkcję, która zwraca dana wartość. Inaczej mówiąc:

F(A×B) → C » F(A) → G(B) → C

Proces odwrotny nosi nazwę uncurring. Nazwa pochodzi od nazwiska Haskella CurregoW

Częściowa aplikacja (ang. partial applicationW)

Jest to proces fiksowania jednego z argumentów funkcji wieloargumentowej w celu zmniejszenia liczby argumentów:

F(A×B) → C » F(n×B) → C

Te dwa pojęcia są ze sobą często mylone ponieważ efektywnie dają podobne rezultaty – funkcję o mniejszej liczbie parametrów. Jest jeszcze częściowa ewaluacjaW, która jest jednak czymś zupełnie innym.

Różnice pomiędzy tymi pojęciami oraz w sumie cały „pałer” funkcji wyższego rzędu w Kotlinie przedstawia poniższy listing:

Listing 1. Currying, częściowa aplikacja i HOF w Kotlinie

    println(sum(2, 3))

    val curried = curr(::sum)
    val partial = first2(::sum)
    val duplicate = first2(::dup)


    println(curried.toString() + " → " + curried(3)(2))
    println(partial.toString() + " → " + partial(3))
    println(duplicate.toString() + " → " + duplicate("A"))

}

fun sum(a: Int, b: Int): Int {
    return a + b
}

fun <T, U, R> curr(f: (T, U) -> R): (T) -> (U) -> R {
    return fun(t: T): (U) -> R {
        return fun(u: U): R {
            return f(t, u)
        }
    }
}

fun <T, R> first2(f:(Int, T) -> R): (T) -> R {
    return fun(b: T): R {
        return f(2, b);
    }
}

Na listingu 1. mamy pełne spektrum możliwości jakie daje nam kotlin. Po pierwsze możemy funkcję przypisać do zmiennej np. curried czy partial. Po drugie możemy przekazać funkcję jako parametr first2(**::sum**). Po trzecie funkcja może być zwrócona jako wynik działania funkcji curr i first2, po czym można ją wywołać przekazując „brakujące” argumenty curried(3)(2) i partial(3).

Oczywiście całość można wykorzystać też w inny sposób:

Listing 2. Duplikacja Stringa

fun main(args: Array<String>) {

    val duplicate = first2(::dup)
    
    println(duplicate("A"))

}

operator fun Int.times(s: String): String {
    fun con(a: String, i: Int): String {
        return a + s;
    }
    return 1.rangeTo(this).fold("", ::con);
}

fun dup(a: Int, b: String): String {
    return a * b
}

fun <T, R> first2(f:(Int, T) -> R): (T) -> R {
    return fun(b: T): R {
        return f(2, b);
    }
}

Mamy tu funkcję wewnętrzną con, która jest przekazywana do fold jako operator. Dodatkowo typ Int rozszerzamy o operację mnożenia Stringów. Mając HOF mamy bardzo duże możliwości w tworzeniu kodu.