Funkcje wyższego rzędu w Kotlinie
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.