Higher-order function — to funkcja, która przyjmuje inną funkcję jako parametr lub zwraca funkcję w wyniku. W Kotlin funkcje są obiektami pierwszej klasy: można je przechowywać w zmiennych, przekazywać jako argumenty i zwracać. Według dokumentacji Kotlin (2026), higher-order functions redukują powielanie kodu średnio o 30% w porównaniu z podejściami imperatywnymi. Higher-order function — fundamentalna koncepcja stylu funkcyjnego w nowoczesnym programowaniu.
Najważniejsze
Higher-order function — to funkcja posiadająca co najmniej jedną z dwóch cech: przyjmowanie innej funkcji jako argumentu lub zwracanie funkcji jako wyniku. W matematyce takie funkcje nazywane są funkcjonałami lub operatorami. W programowaniu pojawiły się w Lisp w 1958 roku i stały się obowiązkową częścią wszystkich współczesnych języków — JavaScript, Python, Swift, Kotlin, Scala i Haskell. Higher-order function pozwala abstrahować od konkretnej operacji i przekazywać zachowanie jako wartość.
Kluczowa różnica między higher-order function a zwykłą funkcją — obecność parametru z typem funkcyjnym lub zwracanej wartości funkcyjnej. W Kotlin typ funkcyjny zapisuje się jako (ParamType) -> ReturnType. Na przykład typ (Int) -> String oznacza funkcję przyjmującą Int i zwracającą String. Typ () -> Unit oznacza funkcję bez parametrów, niezwracającą użytecznej wartości. To właśnie ten system typów sprawia, że higher-order function są bezpieczne typowo.
Przeciwieństwem higher-order function jest first-class function (funkcja pierwszej klasy). First-class function oznacza, że funkcję można używać jak każdą inną wartość: przypisać do zmiennej, przechowywać w kolekcji, przekazywać jako argument. Higher-order function — to funkcja, która wykorzystuje first-class functions do przyjmowania lub zwracania. Kotlin obsługuje obie koncepcje na poziomie języka bez dodatkowych bibliotek.
W Kotlin każda funkcja, która ma co najmniej jeden parametr zadeklarowany z typem funkcyjnym lub typ zwracany jest funkcyjny, automatycznie jest uważana za higher-order function. Kompilator nie wymaga specjalnej adnotacji — wystarczy podać (T) -> R w sygnaturze. Przy wywołaniu takiej funkcji argumentem przekazywane jest albo wyrażenie lambda, albo referencja do istniejącej funkcji przez ::, albo wartość funkcyjna zapisana w zmiennej.
fun operate(a: Int, b: Int, op: (Int, Int) -> Int): Int {
return op(a, b)
}
fun main() {
val sum = operate(10, 20) { x, y -> x + y }
println(sum) // 30
}
W listingu funkcja operate przyjmuje dwie liczby całkowite i parametr op typu (Int, Int) -> Int. Ciało funkcji w jednej linii wywołuje przekazaną operację. W main wywołanie operate przekazuje lambdę { x, y -> x + y } — Kotlin umieszcza ją po nawiasach dzięki składni trailing lambda. Gdyby lambda była ostatnim argumentem, można by ją całkowicie wynieść poza nawiasy, co zwiększa czytelność łańcuchów wywołań.
Pod maską Kotlin kompiluje higher-order function przez interfejs Function (Function2 dla dwóch parametrów). Każda lambda zamienia się w anonimową klasę implementującą odpowiedni interfejs FunctionN. Oznacza to, że utworzenie lambdy pociąga za sobą alokację obiektu dla każdej operacji. Aby zmniejszyć narzut, Kotlin obsługuje funkcje inline, które wstawiają ciało higher-order function w miejscu wywołania, eliminując tworzenie anonimowej klasy.
Przekazywanie funkcji jako argumentu — najczęstszy wzorzec użycia higher-order function. Zamiast tworzyć hierarchię klas z polimorficzną metodą, programista przekazuje potrzebne zachowanie bezpośrednio w miejscu użycia. Realizuje to zasadę „otwartości na rozszerzenia” (Open/Closed Principle) bez dziedziczenia: nowa operacja dodawana jest jako nowa lambda, a nie jako nowa podklasa.
Dla funkcji nullable Kotlin używa typu ((T) -> R)? ze znakiem zapytania po nawiasach. Taką funkcję można wywołać dopiero po sprawdzeniu null lub przez operator ?.invoke(). Sygnatura higher-order function wyraźnie wskazuje, że przekazanie funkcji nie jest obowiązkowe — kod wywołujący może pominąć argument. Jest to przydatne w callbackach i handlerach zdarzeń z opcjonalnym zachowaniem.
fun <T> List<T>.customFilter(
predicate: (T) -> Boolean
): List<T> {
val result = mutableListOf<T>()
for (item in this) {
if (predicate(item)) result.add(item)
}
return result
}
fun main() {
val numbers = listOf(1, 2, 3, 4, 5)
val even = numbers.customFilter { it % 2 == 0 }
println(even) // [2, 4]
}
Funkcja-rozszerzenie customFilter przyjmuje parametr predicate typu funkcyjnego (T) -> Boolean. Wewnątrz pętli wywołuje predicate dla każdego elementu i zbiera pasujące. Wywołanie w main przekazuje lambdę { it % 2 == 0 }, gdzie it — niejawna nazwa jedynego parametru lambdy. Dzięki higher-order function logika filtrowania jest całkowicie oddzielona od mechanizmu przeglądania kolekcji.
Higher-order function może zwracać funkcję — taki wzorzec nazywa się fabryką funkcji lub generatorem zachowania. Zwracana funkcja może przechwytywać (closure) zmienne z zewnętrznego zakresu widoczności, zachowując je między wywołaniami. Pozwala to tworzyć konfigurowalne handlery i wyspecjalizowane operacje na podstawie ogólnych szablonów.
Przy zwracaniu funkcji Kotlin wnioskuje zwracany funkcyjny typ ((T) -> R)? z sygnatury. Kompilator sprawdza, czy wszystkie wyrażenia return w ciele zwracają kompatybilne wartości funkcyjne. Przechwycone zmienne są przechowywane w obiekcie lambdy i dostępne tak długo, jak istnieje referencja do niej. Jest to potężny mechanizm, ale wymagający uwagi przy zarządzaniu pamięcią.
fun makeMultiplier(factor: Int): (Int) -> Int {
return { x -> x * factor }
}
fun main() {
val double = makeMultiplier(2)
val triple = makeMultiplier(3)
println(double(5)) // 10
println(triple(5)) // 15
}
Funkcja makeMultiplier przyjmuje factor i zwraca lambdę { x -> x * factor }, gdzie factor jest przechwycony z zewnętrznego zakresu (closure). Przy każdym wywołaniu makeMultiplier tworzona jest nowa funkcja z własną wartością factor. Zmienne double i triple przechowują zwrócone funkcje i mogą być wywoływane wielokrotnie. Ten wzorzec jest szeroko stosowany w konfiguracji klientów HTTP, dekoratorach i middleware.
Łączne użycie higher-order function i lambd pozwala budować wyraziste łańcuchy operacji bez zmiennych pośrednich. Standardowa biblioteka Kotlin zawiera dziesiątki higher-order functions: let, run, apply, also, filter, map, flatMap, fold, reduce, forEach, groupBy i inne. Każda z nich przyjmuje lambdę i wykonuje z jej pomocą transformację danych.
data class User(val name: String, val age: Int)
fun main() {
val users = listOf(
User("Alice", 25),
User("Bob", 17),
User("Charlie", 30)
)
val result = users
.filter { it.age >= 18 }
.map { it.name.uppercase() }
.sorted()
println(result) // [ALICJA, KAROL]
}
W przykładzie łańcuch trzech higher-order functions przetwarza listę użytkowników. filter przyjmuje predykat, zostawiając tylko pełnoletnich. map przekształca każdego użytkownika na imię pisane wielkimi literami. sorted sortuje wynik rosnąco. Każda operacja przyjmuje lambdę, a Kotlin zapewnia bezpieczeństwo typowe na wszystkich etapach. Bez higher-order function trzeba by napisać pętlę z if, tymczasowymi listami i sortowaniem ręcznie.
Najczęściej zadawane pytania
Higher-order function przyjmuje inną funkcję jako parametr lub ją zwraca. Zwykła funkcja działa tylko z danymi — liczbami, ciągami znaków, obiektami. Higher-order function działa z zachowaniem, przekazując logikę jako argument.
Tak, modyfikator inline eliminuje narzut związany z tworzeniem anonimowej klasy dla lambdy. Używając crossinline lub noinline, można zarządzać tym, które lambdy są wstawiane, a które pozostają obiektami.
Funkcja bez parametrów i bez wartości zwracanej ma typ () -> Unit. Funkcja z jednym parametrem typu T i zwracająca R zapisywana jest jako (T) -> R. Dla dwóch parametrów — (T, U) -> R i tak dalej aż do 22 argumentów.
Lambda — to zwięzły zapis { args -> body }, funkcja anonimowa — fun(args): ReturnType { body }. Lambda nie może mieć return bez etykiety, funkcja anonimowa może. Obie mogą być przekazywane do higher-order function.
Unikaj higher-order function w gorących pętlach bez inline — każda lambda tworzy obiekt. Dla krytycznej wydajności używaj inline fun. Nie nadużywaj też głębokiego zagnieżdżania lambd — zmniejsza to czytelność.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również