Higher-order function — це функція, яка приймає іншу функцію як параметр або повертає функцію як результат. В Kotlin функції є об’єктами першого класу: їх можна зберігати в змінних, передавати аргументами та повертати. Згідно з документацією Kotlin (2026), higher-order functions скорочують дублювання коду в середньому на 30% порівняно з імперативними підходами. Higher-order function — фундаментальна концепція функціонального стилю в сучасному розробці.
Головне
Higher-order function — це функція, яка має хоча б одну з двох характеристик: приймання іншої функції як аргументу або повернення функції як результату. В математиці такі функції називають функціоналами або операторами. В програмуванні вони з’явилися в Lisp у 1958 році і стали обов’язковою частиною всіх сучасних мов — JavaScript, Python, Swift, Kotlin, Scala та Haskell. Higher-order function дозволяє абстрагуватися від конкретної операції та передавати поведінку як значення.
Ключова відмінність higher-order function від звичайної функції — наявність параметра з функціональним типом або поверненого функціонального значення. В Kotlin функціональний тип записується як (ParamType) -> ReturnType. Наприклад, тип (Int) -> String означає функцію, яка приймає Int і повертає String. Тип () -> Unit позначає функцію без параметрів, яка не повертає корисного значення. Саме ця система типів робить higher-order function типобезпечними.
Протилежність higher-order function — first-class function (функція першого класу). First-class function означає, що функцію можна використовувати як будь-яке інше значення: присвоїти змінній, зберігати в колекції, передавати аргументом. Higher-order function — це функція, яка використовує first-class functions для приймання або повернення. Kotlin підтримує обидві концепції на рівні мови без додаткових бібліотек.
В Kotlin будь-яка функція, яка має хоча б один параметр, оголошений з функціональним типом, або чий повернений тип є функціональним, автоматично вважається higher-order function. Компілятор не вимагає спеціальної анотації — достатьо вказати (T) -> R в сигнатурі. При виклику такої функції аргументом передається або лямбда-вираз, або посилання на існуючу функцію через ::, або функціональне значення, збережене в змінній.
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
}
У лістингу функція operate приймає два цілі числа та параметр op типу (Int, Int) -> Int. Тіло функції однією рядкою викликає передану операцію. У main виклик operate передає лямбду { x, y -> x + y } — Kotlin розміщує її після дужок завдяки синтаксису trailing lambda. Якби лямбда була останнім аргументом, її можна було б повністю винести за дужки, що підвищує читабельність ланцюжків викликів.
Під капотом Kotlin компілює higher-order functions через інтерфейс Function (Function2 для двох параметрів). Кожна лямбда перетворюється на анонімний клас, що реалізує відповідний інтерфейс FunctionN. Це означає, що створення лямбди тягне за собою виділення об’єкта на кожну операцію. Для зменшення накладних витрат Kotlin підтримує inline-функції, які підставляють тіло higher-order function на місці виклику, усунаючи створення анонімного класу.
Передача функції як аргументу — найпоширеніший патерн використання higher-order functions. Замість створення ієрархії класів з поліморфним методом, розробник передає бажану поведінку безпосередньо в точку використання. Це реалізує принцип відкритості для розширення (Open/Closed Principle) без спадкування: нова операція додається як нова лямбда, а не як новий підклас.
Для nullable-функцій Kotlin використовує тип ((T) -> R)? з питальним знаком після дужок. Таку функцію можна викликати лише після null-перевірки або через оператор ?.invoke(). При цьому сигнатура higher-order function явно вказує, що передача функції необов’язкова — викликаючий код може опустити аргумент. Це корисно для колбеків та обробників подій з опціональною поведінкою.
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]
}
Функція-розширення customFilter приймає параметр predicate функціонального типу (T) -> Boolean. Всередині циклу вона викликає predicate для кожного елемента та збирає підходячі. Виклик у main передає лямбду { it % 2 == 0 }, де it — неявне ім’я єдиного параметра лямбди. Завдяки higher-order functions логіка фільтрації повністю ізольована від механізму обходу колекції.
Higher-order function може повертати функцію — такий патерн називають фабрикою функцій або генератором поведінки. Повернена функція може захоплювати (closure) змінні з зовнішньої області видимості, зберігаючи їх між викликами. Це дозволяє створювати конфігуровані обробники та спеціалізовані операції на основі загальних шаблонів.
При поверненні функції Kotlin виводить повернений функціональний тип ((T) -> R)? з сигнатури. Компілятор перевіряє, що всі return-вирази в тілі повертають сумісні функціональні значення. Захоплені змінні зберігаються в об’єкті лямбди і доступні, поки існує посилання на нього. Це потужний механізм, але він вимагає уваги до управління пам’яттю.
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
}
Функція makeMultiplier приймає factor та повертає лямбду { x -> x * factor }, де factor захоплений з зовнішньої області (closure). Кожен виклик makeMultiplier створює нову функцію з власним значенням factor. Змінні double та triple зберігають повернені функції та можуть викликатися багаторазово. Цей патерн широко застосовується в конфігурації HTTP-клієнтів, декораторах та програмному прошарку.
Спільне використання higher-order functions та лямбд дозволяє будувати виразні ланцюжки операцій без проміжних змінних. Стандартна бібліотека Kotlin містить десятки higher-order functions: let, run, apply, also, filter, map, flatMap, fold, reduce, forEach, groupBy та інші. Кожна з них приймає лямбду та виконує з її допомогою перетворення даних.
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) // [ALICE, CHARLIE]
}
У прикладі ланцюжка з трьох higher-order functions обробляє список користувачів. filter приймає предикат, залишаючи лише повнолітніх. map перетворює кожного користувача на ім’я в верхньому регістрі. sorted сортує результат за зростанням. Кожна операція приймає лямбду, а Kotlin забезпечує типобезпеку на всіх етапах. Без higher-order functions довелося б писати цикл з if, тимчасовими списками та ручним сортуванням.
Часто задавані питання
Higher-order function приймає іншу функцію як параметр або повертає її. Звичайна функція працює лише з даними — числами, рядками, об’єктами. Higher-order function працює з поведінкою, передаючи логіку як аргумент.
Так, модифікатор inline усунає накладні витрати на створення анонімного класу для лямбди. При використанні crossinline або noinline можна керувати, які лямбди підставляються, а які залишаються об’єктами.
Функція без параметрів і без поверненого значення має тип () -> Unit. Функція з одним параметром типу T та поверненням R записується як (T) -> R. Для двох параметрів — (T, U) -> R, і так далі до 22 аргументів.
Лямбда — компактний запис { args -> body }, анонімна функція — fun(args): ReturnType { body }. Лямбда не може мати return без мітки, анонімна функція може. Обидві можуть передаватися в higher-order function.
Уникайте higher-order functions в гарячих циклах без inline — кожна лямбда створює об’єкт. Для критичного для продуктивності коду використовуйте inline fun. Також не зловживайте глибоко вкладеними лямбдами — це знижує читабельність.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також