Programmation fonctionnelle en Go : de A à Z
Go (Golang) est un langage souvent associé à la programmation impérative et concurrente. Cependant, son système de types flexible et ses fonctions de première classe permettent d'appliquer avec succès le style fonctionnel. Dans cet article, nous allons voir comment utiliser la programmation fonctionnelle (PF) en Go : des concepts de base aux exemples pratiques. Vous apprendrez à écrire un code plus propre, plus prévisible et plus testable.
Qu'est-ce que la programmation fonctionnelle dans le contexte de Go ?
La programmation fonctionnelle est un paradigme où les calculs sont construits sur l'application de fonctions, en évitant la modification d'état et les effets secondaires. En Go, cela se traduit par :
- Fonctions pures — le résultat dépend uniquement des arguments, pas de mutation de données externes.
- Fonctions d'ordre supérieur — des fonctions qui prennent d'autres fonctions comme arguments ou les retournent.
- Fermetures — des fonctions anonymes qui « mémorisent » les variables de la portée externe.
- Immutabilité des données — préférence pour la copie plutôt que la modification.
Bien que Go ne soit pas un langage purement fonctionnel (il n'a pas de monades ou de types de données algébriques), il fournit suffisamment d'outils pour utiliser efficacement les approches PF.
Fonctions anonymes et fermetures
En Go, les fonctions sont des objets de première classe. On peut les assigner à des variables, les passer à d'autres fonctions et les retourner. Les fonctions anonymes (lambdas) sont la base du style fonctionnel.
Exemple de fonction anonyme
package main
import "fmt"
func main() { // Fonction anonyme assignée à une variable multiply := func(x, y int) int { return x * y }
result := multiply(5, 3) fmt.Println(result) // 15}Fermetures (Closures)
Une fermeture est une fonction anonyme qui « capture » des variables de la portée externe. C'est un outil puissant pour créer des générateurs et des fonctions avec état.
package main
import "fmt"
// Générateur de séquencefunc sequence() func() int { i := 0 return func() int { i++ return i }}
func main() { next := sequence() fmt.Println(next()) // 1 fmt.Println(next()) // 2 fmt.Println(next()) // 3
// Chaque appel à sequence() crée une nouvelle fermeture next2 := sequence() fmt.Println(next2()) // 1}Les fermetures sont idéales pour encapsuler l'état sans utiliser de variables globales — c'est la base de l'approche fonctionnelle en Go.
Fonctions pures et immutabilité
Une fonction pure n'a pas d'effets secondaires et ne modifie pas les données transmises. En Go, cela nécessite une approche consciente, car les slices et les maps sont passées par référence.
Exemple de fonction pure (sans mutation)
package main
import "fmt"
// Fonction pure : ne modifie pas la slice d'originefunc doubleValues(input []int) []int { result := make([]int, len(input)) for i, v := range input { result[i] = v * 2 } return result}
func main() { original := []int{1, 2, 3, 4} doubled := doubleValues(original)
fmt.Println("Original:", original) // [1 2 3 4] fmt.Println("Doubled:", doubled) // [2 4 6 8]}Remarquez : nous créons une nouvelle slice au lieu de modifier l'originale. Cela garantit l'immutabilité — un principe clé de la PF.
Fonctions d'ordre supérieur : map, filter, reduce
Bien que Go n'ait pas de méthodes intégrées map/filter/reduce pour les slices, il est facile de les implémenter soi-même.
package main
import "fmt"
// Map — applique une fonction à chaque élémentfunc Map[T any](items []T, fn func(T) T) []T { result := make([]T, len(items)) for