Leçons du module (3/5)
Génériques (Go 1.18+)
Depuis Go 1.18 (mars 2022), des paramètres de type sont disponibles : les fonctions et les types peuvent être paramétrés sur les types. Ils vous permettent d'écrire Map, Filter, Set[T], LinkedList[T] sans dupliquer le code et sans assertions de type d'exécution interface{} +.
Fonction générique : la syntaxe
func Map[T, U any](s []T, f func(T) U) []U {
out := make([]U, len(s))
for i, v := range s {
out[i] = f(v)
}
return out
}
doubled := Map([]int{1, 2, 3}, func(n int) int { return n * 2 })
// doubled is []int{2, 4, 6}Trois nouveaux éléments :
[T, U any]après le nom : déclare les paramètres de type avec leurs contraintes.any= contrainte qui accepte tout type (alias deinterface{}depuis Go 1.18).- Inférence : en appelant
Map([]int{...}, ...), vous n'avez pas besoin d'écrireMap[int, int](...): le compilateur déduitT=int, U=int.
Parfois, l'inférence n'est pas suffisante (par exemple, des constructeurs sans arguments T) et une instanciation explicite est nécessaire : New[*Server]().
Contraintes intégrées
| Contrainte | Signification |
|---|---|
any | Tout type (== interface{}). |
comparable | Types avec == et != : numérique, chaîne, booléen, pointeurs, canaux, tableaux/structures comparables. |
comparable autorise l'égalité mais pas < ou >.
func Index[T comparable](s []T, x T) int {
for i, v := range s {
if v == x { return i }
}
return -1
}Pour </>, vous avez besoin d'une contrainte personnalisée.
Contrainte personnalisée : tapez les unions
Une contrainte est une interface avec des clauses "type element" :
type Ordered interface {
~int | ~int64 | ~float64 | ~string
}
func Min[T Ordered](a, b T) T {
if a < b { return a }
return b
}|= union : accepte n'importe lequel des types répertoriés.~int= "int ou tout type défini commetype X int" (avec le même type sous-jacent). Sans~, seulementintexact.
Une contrainte peut également mélanger des méthodes et des éléments de type :
type Stringable interface {
~string
Len() int
}Types génériques
Pas seulement les fonctions : les structures et les interfaces peuvent également avoir des paramètres de type.
type Set[T comparable] struct {
m map[T]struct{}
}
func NewSet[T comparable]() *Set[T] {
return &Set[T]{m: make(map[T]struct{})}
}
func (s *Set[T]) Add(v T) { s.m[v] = struct{}{} }
func (s *Set[T]) Has(v T) bool { _, ok := s.m[v]; return ok }
s := NewSet[string]()
s.Add("ada")Les méthodes NE PEUVENT PAS ajouter de paramètres de type au-delà de ceux du type : func (s *Set[T]) MapTo[U any](...) ne compile pas (limitation intentionnelle de la conception).
Quand utiliser des génériques
Oui quand :
- La logique est vraiment identique pour plusieurs types (collections, algorithmes, helpers comme
Map/Filter/Reduce). - L'alternative serait de dupliquer le code ou d'utiliser des assertions de type
interface{}+ (perte de la sécurité du type).
Non quand :
- Une interface avec une ou deux méthodes suffit et plus lisible (
io.Reader,fmt.Stringer). - Vous paramétrez "parce que vous pouvez" : un seul cas d'usage → en faire une version concrète.
Exercices
Définit la fonction générique Min[T Ordered](a, b T) T qui ritorna le mineur en utilisant l'opérateur <. La contrainte Ordonné est già fornito.
Solution disponible après 3 tentatives
Implémentez Map[T, U any](s []T, f func(T) U) []U : appliquez chaque élément et restituez la nouvelle tranche.
Solution disponible après 3 tentatives
Quelle contrainte intégrée autorise l'opérateur == ma NON l'opérateur <?
func Index[T ???](s []T, x T) int { ... }