Une higher-order function est une fonction qui prend une autre fonction comme paramètre ou retourne une fonction en résultat. En Kotlin, les fonctions sont des citoyens de première classe : elles peuvent être stockées dans des variables, passées comme arguments et retournées. Selon la documentation Kotlin (2026), les higher-order functions réduisent la duplication de code de 30% en moyenne par rapport aux approches impératives. Higher-order function est un concept fondamental du style fonctionnel dans le développement moderne.
Points clés
Une higher-order function est une fonction qui possède au moins l’une des deux caractéristiques suivantes : prendre une autre fonction comme argument ou retourner une fonction comme résultat. En mathématiques, ces fonctions sont appelées fonctionnelles ou opérateurs. En programmation, elles sont apparues dans Lisp en 1958 et sont devenues une partie obligatoire de tous les langages modernes : JavaScript, Python, Swift, Kotlin, Scala et Haskell. Une higher-order function permet de s’abstraire d’une opération spécifique et de passer un comportement comme valeur.
La différence clé entre une higher-order function et une fonction ordinaire est la présence d’un paramètre avec un type fonctionnel ou d’une valeur fonctionnelle retournée. En Kotlin, un type fonctionnel s’écrit (ParamType) -> ReturnType. Par exemple, le type (Int) -> String signifie une fonction qui prend Int et retourne String. Le type () -> Unit dénote une fonction sans paramètres qui ne retourne aucune valeur utile. C’est ce système de types qui rend les higher-order functions type-safe.
L’opposé d’une higher-order function est une first-class function (fonction de première classe). First-class function signifie qu’une fonction peut être utilisée comme n’importe quelle autre valeur : assignée à une variable, stockée dans une collection, passée en argument. Une higher-order function est une fonction qui utilise des first-class functions pour accepter ou retourner. Kotlin prend en charge les deux concepts au niveau du langage sans bibliothèques supplémentaires.
En Kotlin, toute fonction qui a au moins un paramètre déclaré avec un type fonctionnel ou dont le type de retour est fonctionnel est automatiquement considérée comme une higher-order function. Le compilateur ne nécessite pas d’annotation spéciale : il suffit de spécifier (T) -> R dans la signature. Lors de l’appel d’une telle fonction, l’argument est soit une expression lambda, soit une référence à une fonction existante via ::, soit une valeur fonctionnelle stockée dans une variable.
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
}
Dans le listing, la fonction operate prend deux entiers et un paramètre op de type (Int, Int) -> Int. Le corps de la fonction appelle l’opération passée en une seule ligne. Dans main, l’appel à operate passe le lambda { x, y -> x + y } — Kotlin le place après les parenthèses grâce à la syntaxe trailing lambda. Si le lambda était le dernier argument, il pourrait être complètement déplacé en dehors des parenthèses, ce qui améliore la lisibilité des chaînes d’appels.
Sous le capot, Kotlin compile les higher-order functions via l’interface Function (Function2 pour deux paramètres). Chaque lambda se transforme en une classe anonyme qui implémente l’interface FunctionN correspondante. Cela signifie que la création d’un lambda implique une allocation d’objet pour chaque opération. Pour réduire la surcharge, Kotlin prend en charge les fonctions inline, qui substituent le corps de la higher-order function au site d’appel, éliminant la création de la classe anonyme.
Passer une fonction comme argument est le motif le plus courant d’utilisation des higher-order functions. Au lieu de créer une hiérarchie de classes avec une méthode polymorphe, le développeur passe le comportement souhaité directement au point d’utilisation. Cela implémente le principe Ouvert/Fermé sans héritage : une nouvelle opération est ajoutée comme un nouveau lambda, pas comme une nouvelle sous-classe.
Pour les fonctions nullables, Kotlin utilise le type ((T) -> R)? avec un point d’interrogation après les parenthèses. Une telle fonction ne peut être appelée qu’après une vérification de null ou via l’opérateur ?.invoke(). Dans ce cas, la signature de la higher-order function indique explicitement que passer une fonction est optionnel : le code appelant peut omettre l’argument. C’est utile pour les callbacks et les gestionnaires d’événements avec un comportement optionnel.
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]
}
La fonction d’extension customFilter prend un paramètre predicate de type fonctionnel (T) -> Boolean. À l’intérieur de la boucle, elle appelle predicate pour chaque élément et collecte ceux qui correspondent. L’appel dans main passe le lambda { it % 2 == 0 }, où it est le nom implicite du seul paramètre du lambda. Grâce aux higher-order functions, la logique de filtrage est complètement isolée du mécanisme de parcours de la collection.
Une higher-order function peut retourner une fonction — ce motif est appelé fabrique de fonctions ou générateur de comportement. La fonction retournée peut capturer (closure) des variables de la portée externe, les préservant entre les appels. Cela permet de créer des gestionnaires configurables et des opérations spécialisées basés sur des modèles courants.
Lors du retour d’une fonction, Kotlin déduit le type fonctionnel ((T) -> R)? retourné à partir de la signature. Le compilateur vérifie que toutes les expressions return dans le corps retournent des valeurs fonctionnelles compatibles. Les variables capturées sont stockées dans l’objet lambda et restent accessibles tant qu’une référence existe. C’est un mécanisme puissant, mais qui nécessite une attention particulière à la gestion de la mémoire.
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
}
La fonction makeMultiplier prend un facteur et retourne un lambda { x -> x * factor }, où factor est capturé de la portée externe (closure). Chaque appel à makeMultiplier crée une nouvelle fonction avec sa propre valeur de factor. Les variables double et triple stockent les fonctions retournées et peuvent être appelées plusieurs fois. Ce motif est largement utilisé dans la configuration de clients HTTP, les décorateurs et les middleware.
La combinaison de higher-order functions et de lambdas permet de construire des chaînes d’opérations expressives sans variables intermédiaires. La bibliothèque standard de Kotlin contient des dizaines de higher-order functions : let, run, apply, also, filter, map, flatMap, fold, reduce, forEach, groupBy et d’autres. Chacune prend un lambda et effectue une transformation de données avec celui-ci.
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]
}
Dans l’exemple, une chaîne de trois higher-order functions traite une liste d’utilisateurs. filter prend un prédicat, ne gardant que les adultes. map transforme chaque utilisateur en un nom en majuscules. sorted trie le résultat par ordre croissant. Chaque opération prend un lambda, et Kotlin assure la sécurité des types à chaque étape. Sans higher-order functions, il faudrait écrire une boucle avec if, des listes temporaires et un tri manuel.
Foire aux questions
Une higher-order function prend une autre fonction comme paramètre ou en retourne une. Une fonction ordinaire travaille uniquement avec des données : nombres, chaînes, objets. Une higher-order function travaille avec le comportement, passant la logique comme argument.
Oui, le modificateur inline élimine la surcharge de création d’une classe anonyme pour un lambda. En utilisant crossinline ou noinline, on peut contrôler quels lambdas sont substitués et lesquels restent des objets.
Une fonction sans paramètres et sans valeur de retour a le type () -> Unit. Une fonction avec un paramètre de type T et un retour R s’écrit (T) -> R. Pour deux paramètres : (T, U) -> R, et ainsi de suite jusqu’à 22 arguments.
Un lambda est une notation concise { args -> body }, une fonction anonyme est fun(args): ReturnType { body }. Un lambda ne peut pas utiliser return sans étiquette, une fonction anonyme le peut. Les deux peuvent être passés à une higher-order function.
Évitez les higher-order functions dans les boucles critiques sans inline : chaque lambda crée un objet. Pour le code critique en performance, utilisez inline fun. N’abusez pas non plus des lambdas profondément imbriqués — cela réduit la lisibilité.
Résumé
Nous développerons une application mobile clé en main
IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.
Lisez aussi