lo pour Go : guide complet avec exemples

Formateur Python en ligne pour débutants

Apprenez Python facilement sans surcharge théorique. Résolvez des tâches pratiques avec vérification automatique et écrivez du code directement dans le navigateur.

Start Course

Bibliothèque lo pour Go : utilitaires génériques (Lodash pour Go)

Dans le monde du développement en Go, on ressent souvent le besoin de fonctions pratiques pour manipuler les collections, comme celles disponibles dans d'autres langages (par exemple, map, filter, reduce en JavaScript ou Python). La bibliothèque standard de Go offre un ensemble d'outils minimal, et écrire des boucles for répétitives pour chaque opération devient fastidieux. C'est là que la bibliothèque lo intervient — un ensemble puissant d'utilitaires génériques, inspiré de la célèbre bibliothèque JavaScript Lodash.

La bibliothèque lo utilise les génériques (types paramétrés), introduits dans Go 1.18+, pour fournir des fonctions type-safe pour travailler avec des slices, des maps, des channels et d'autres structures de données. Au lieu d'écrire for i := range slice { ... } pour chaque transformation, vous pouvez utiliser une seule ligne de code : lo.Map(slice, transformFunc). Cela rend le code plus lisible, concis et moins sujet aux erreurs.

Pourquoi utiliser lo ? Premièrement, pour gagner du temps : vous arrêtez d'écrire du code boilerplate. Deuxièmement, pour améliorer la fiabilité : les fonctions de la bibliothèque sont minutieusement testées et couvrent de nombreux cas particuliers. Troisièmement, pour l'uniformité : toute l'équipe utilise les mêmes fonctions avec des noms clairs, ce qui simplifie les revues de code. lo est un incontournable pour tout projet Go qui manipule activement des collections de données.

Installation

L'installation de la bibliothèque lo se fait de manière standard pour Go via go get. Assurez-vous d'avoir installé Go version 1.18 ou supérieure (car la bibliothèque utilise les génériques).

go get github.com/samber/lo@latest

Après avoir exécuté cette commande, la bibliothèque sera ajoutée à votre fichier go.mod. Vous pouvez maintenant l'importer dans votre code :

import "github.com/samber/lo"

Démarrage rapide

Écrivons un programme simple qui utilise lo pour filtrer et transformer un slice de nombres.

package main

import (
    "fmt"
    "github.com/samber/lo"
)

func main() {
    // Slice de nombres initial
    numbers := []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

    // Filtrer uniquement les nombres pairs
    evenNumbers := lo.Filter(numbers, func(x int, _ int) bool {
        return x%2 == 0
    })
    fmt.Println("Nombres pairs :", evenNumbers) // [2 4 6 8 10]

    // Doubler chaque nombre
    doubled := lo.Map(evenNumbers, func(x int, _ int) int {
        return x * 2
    })
    fmt.Println("Nombres pairs doublés :", doubled) // [4 8 12 16 20]

    // Additionner tous les nombres
    sum := lo.Sum(doubled)
    fmt.Println("Somme :", sum) // 60
}

Comme vous pouvez le voir, le code est devenu beaucoup plus déclaratif et lisible. Nous avons évité d'écrire trois boucles for distinctes.

Méthodes et fonctions principales

Voici une liste des fonctions clés de la bibliothèque lo avec des exemples d'utilisation. La plupart des fonctions prennent un slice et une fonction de rappel (callback).

1. lo.Filter

Signature : func Filter[T any](collection []T, predicate func(item T, index int) bool) []T

Fonction : Crée un nouveau slice contenant uniquement les éléments du slice original pour lesquels la fonction de prédicat a retourné true.

package main

import (
    "fmt"
    "github.com/samber/lo"
)

func main() {
    names := []string{"Alice", "Bob", "Charlie", "David", "Eve"}
    // Garder uniquement les noms de plus de 3 caractères
    longNames := lo.Filter(names, func(name string, _ int) bool {
        return len(name) > 3
    })
    fmt.Println(longNames) // [Alice Charlie David]
}

2. lo.Map

Signature : func Map[T any, R any](collection []T, iteratee func(item T, index int) R) []R

Fonction : Transforme chaque élément d'un slice en un nouvel élément d'un autre type, en appliquant une fonction de transformation.

package main

import (
    "fmt"
    "github.com/samber/lo"
)

func main() {
    numbers := []int{1, 2, 3, 4, 5}
    // Convertir chaque nombre en chaîne de caractères
    strings := lo.Map(numbers, func(x int, _ int) string {
        return fmt.Sprintf("Nombre : %d", x)
    })
    fmt.Println(strings) // [Nombre : 1 Nombre : 2 Nombre : 3 Nombre : 4 Nombre : 5]
}

3. lo.Reduce

Signature : func Reduce[T any, R any](collection []T, accumulator func(agg R, item T, index int) R, initial R) R

Fonction : Réduit un slice à une seule valeur en appliquant une fonction d'accumulation de gauche à droite.

package main

import (
    "fmt"
    "github.com/samber/lo"
)

func main() {
    numbers := []int{1, 2, 3, 4, 5}
    // Calculer le produit de tous les nombres
    product := lo.Reduce(numbers, func(agg int, item int, _ int) int {
        return agg * item
    }, 1)
    fmt.Println("Produit :", product) // 120
}

4. lo.Find

Signature : func Find[T any](collection []T, predicate func(item T) bool) (T, bool)

Fonction : Recherche le premier élément du slice qui satisfait le prédicat. Retourne l'élément et un booléen indiquant si un élément a été trouvé.

package main

import (
    "fmt"
    "github.com/samber/lo"
)

func main() {
    numbers := []int{10, 20, 30, 40, 50}
    // Trouver le premier nombre supérieur à 25
    found, ok := lo.Find(numbers, func(x int) bool {
        return x > 25
    })
    if ok {
        fmt.Println("Trouvé :", found) // 30
    } else {
        fmt.Println("Aucun élément trouvé")
    }
}

5. lo.Uniq

Signature : func Uniq[T comparable](collection []T) []T

Fonction : Supprime les doublons d'un slice et retourne un nouveau slice avec des éléments uniques, en conservant l'ordre d'apparition.

package main

import (
    "fmt"
    "github.com/samber/lo"
)

func main() {
    numbers := []int{1, 2, 2, 3, 3, 3, 4, 5, 5}
    uniqueNumbers := lo.Uniq(numbers)
    fmt.Println("Nombres uniques :", uniqueNumbers) // [1 2 3 4 5]
}

6. lo.Chunk

Signature : func Chunk[T any](collection []T, size int) [][]T

Fonction : Divise un slice en plusieurs sous-slices de taille spécifiée (sauf le dernier qui peut être plus petit).

package main

import (
    "fmt"
    "github.com/samber/lo"
)

func main() {
    numbers := []int{1, 2, 3, 4, 5, 6, 7}
    chunks := lo.Chunk(numbers, 3)
    fmt.Println("Morceaux :", chunks) // [[1 2 3] [4 5 6] [7]]
}

7. lo.Flatten

Signature : func Flatten[T any](collection [][]T) []T

Fonction : Aplatit un slice de slices en un seul slice.

package main

import (
    "fmt"
    "github.com/samber/lo"
)

func main() {
    nested := [][]int{{1, 2}, {3, 4, 5}, {6}}
    flat := lo.Flatten(nested)
    fmt.Println("Aplati :", flat) // [1 2 3 4 5 6]
}

8. lo.Sum

Signature : func Sum[T constraints.Integer | constraints.Float](collection []T) T

Fonction : Calcule la somme de tous les éléments d'un slice de nombres.

package main

import (
    "fmt"
    "github.com/samber/lo"
)

func main() {
    numbers := []float64{1.5, 2.5, 3.0}
    total := lo.Sum(numbers)
    fmt.Println("Somme :", total) // 7.0
}

9. lo.Contains

Signature : func Contains[T comparable](collection []T, element T) bool

Fonction : Vérifie si un élément est présent dans le slice.

package main

import (
    "fmt"
    "github.com/samber/lo"
)

func main() {
    fruits := []string{"pomme", "banane", "cerise"}
    hasBanana := lo.Contains(fruits, "banane")
    fmt.Println("Contient banane ?", hasBanana) // true

    hasMango := lo.Contains(fruits, "mangue")
    fmt.Println("Contient mangue ?", hasMango) // false
}

10. lo.Keys et lo.Values

Signatures :

  • func Keys[K comparable, V any](m map[K]V) []K
  • func Values[K comparable, V any](m map[K]V) []V

Fonctions : Extraient respectivement les clés et les valeurs d'une map sous forme de slices.

package main

import (
    "fmt"
    "github.com/samber/lo"
)

func main() {
    ages := map[string]int{"Alice": 30, "Bob": 25, "Charlie": 35}
    
    keys := lo.Keys(ages)
    fmt.Println("Clés :", keys) // [Alice Bob Charlie] (ordre non garanti)
    
    values := lo.Values(ages)
    fmt.Println("Valeurs :", values) // [30 25 35] (ordre non garanti)
}

Fonctions avancées

En plus des fonctions de base, lo propose des utilitaires plus avancés pour des cas d'utilisation spécifiques.

11. lo.GroupBy

Signature : func GroupBy[T any, K comparable](collection []T, iteratee func(item T) K) map[K][]T

Fonction : Groupe les éléments d'un slice selon une clé calculée par une fonction.

package main

import (
    "fmt"
    "github.com/samber/lo"
)

func main() {
    words := []string{"pomme", "banane", "poire", "abricot", "cerise"}
    // Grouper par première lettre
    grouped := lo.GroupBy(words, func(word string) string {
        return string(word[0])
    })
    fmt.Println("Groupé :", grouped)
    // Sortie possible : map[a:[abricot] b:[banane] c:[cerise] p:[pomme poire]]
}

12. lo.SortBy

Signature : func SortBy[T any, K constraints.Ordered](collection []T, iteratee func(item T) K) []T

Fonction : Trie un slice selon une clé calculée par une fonction.

package main

import (
    "fmt"
    "github.com/samber/lo"
)

func main() {
    people := []struct {
        Name string
        Age  int
    }{
        {"Alice", 30},
        {"Bob", 25},
        {"Charlie", 35},
    }
    // Trier par âge
    sorted := lo.SortBy(people, func(p struct{ Name string; Age int }) int {
        return p.Age
    })
    fmt.Println("Trié par âge :", sorted)
    // [{Bob 25} {Alice 30} {Charlie 35}]
}

13. lo.PartitionBy

Signature : func PartitionBy[T any, K comparable](collection []T, iteratee func(item T) K) [][]T

Fonction : Divise un slice en sous-slices basés sur une clé calculée. Les éléments consécutifs avec la même clé sont regroupés.

package main

import (
    "fmt"
    "github.com/samber/lo"
)

func main() {
    numbers := []int{1, 2, 2, 3, 3, 3, 4, 5, 5}
    // Partitionner par valeur (les éléments égaux consécutifs sont regroupés)
    partitions := lo.PartitionBy(numbers, func(x int) int {
        return x
    })
    fmt.Println("Partitions :", partitions) // [[1] [2 2] [3 3 3] [4] [5 5]]
}

Fonctions pour les channels

lo propose également des fonctions pour travailler avec les channels Go, facilitant les opérations concurrentes.

14. lo.ChannelDispatcher

Signature : func ChannelDispatcher[T any](stream <-chan T, nbWorkers int, bufferSize int) []<-chan T

Fonction : Distribue les messages d'un channel d'entrée vers plusieurs channels de sortie (workers), en mode round-robin.

package main

import (
    "fmt"
    "github.com/samber/lo"
)

func main() {
    input := make(chan int, 10)
    for i := 1; i <= 6; i++ {
        input <- i
    }
    close(input)

    // Distribuer sur 3 workers
    workers := lo.ChannelDispatcher(input, 3, 10)
    
    for i, worker := range workers {
        go func(id int, ch <-chan int) {
            for val := range ch {
                fmt.Printf("Worker %d a reçu : %d\n", id, val)
            }
        }(i, worker)
    }
    
    // Attendre un peu pour voir les résultats
    // (dans un vrai programme, utilisez sync.WaitGroup)
}

Conclusion

La bibliothèque lo est un outil extrêmement puissant pour tout développeur Go. Elle apporte une touche de programmation fonctionnelle et rend le code plus propre, plus lisible et plus maintenable. Que vous travailliez avec des slices, des maps, des channels ou d'autres structures de données, lo vous offre des fonctions prêtes à l'emploi qui vous feront gagner un temps précieux.

N'hésitez pas à explorer la documentation officielle de lo sur GitHub pour découvrir toutes les fonctions disponibles. Avec plus de 70 utilitaires, vous trouverez certainement une solution élégante pour chaque problème de manipulation de collections en Go.

Alors, prêt à dire adieu aux boucles for répétitives ? Essayez lo dès aujourd'hui et transformez votre façon d'écrire du Go !

Recommendations