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).
Contents
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.