Menu
Coddy logo textTech

Introduction aux génériques

Fait partie de la section Programmation Orientée Objet du Journey GO de Coddy — leçon 67 sur 107.

Avant Go 1.18, écrire du code réutilisable pour différents types signifiait soit dupliquer des fonctions, soit utiliser l'interface vide any avec des assertions de type. Les génériques résolvent ce problème en vous permettant d'écrire des fonctions et des types qui fonctionnent avec plusieurs types tout en conservant une sécurité de type complète.

Considérez la recherche de la valeur minimale dans un slice. Sans les génériques, vous auriez besoin de fonctions distinctes pour chaque type :

func MinInt(values []int) int {
    min := values[0]
    for _, v := range values {
        if v < min {
            min = v
        }
    }
    return min
}

func MinFloat64(values []float64) float64 {
    min := values[0]
    for _, v := range values {
        if v < min {
            min = v
        }
    }
    return min
}

Avec les génériques, vous écrivez la logique une seule fois en utilisant un paramètre de type entre crochets :

func Min[T int | float64](values []T) T {
    min := values[0]
    for _, v := range values {
        if v < min {
            min = v
        }
    }
    return min
}

Le [T int | float64] déclare un paramètre de type T qui peut être soit int, soit float64. Maintenant, vous pouvez appeler Min([]int{3, 1, 2}) ou Min([]float64{3.5, 1.2}) avec la même fonction. Le compilateur vérifie les types au moment de la compilation, vous bénéficiez donc d'une sécurité sans surcoût à l'exécution.

Dans les prochaines leçons, nous explorerons en détail les paramètres de type, les contraintes et les structures génériques.

challenge icon

Défi

Facile

Construisons un analyseur de scores qui fonctionne à la fois avec des scores entiers et à virgule flottante en utilisant les génériques ! Vous allez créer une fonction générique unique capable de trouver la valeur maximale dans une tranche (slice), éliminant ainsi le besoin de dupliquer le code pour différents types numériques.

Vous organiserez votre code sur deux fichiers :

  • analyzer.go : Définissez votre fonction d'analyse générique.

    Créez une fonction générique Max[T int | float64](values []T) T qui trouve et retourne la valeur maximale dans une tranche. La fonction doit fonctionner avec les types int et float64 via le paramètre de type T.

    Créez également une fonction générique Sum[T int | float64](values []T) T qui calcule et retourne la somme de toutes les valeurs d'une tranche.

  • main.go : Lisez l'entrée et démontrez vos fonctions génériques avec différents types.

    Lisez un indicateur de type (int ou float), puis lisez un nombre suivi de ce même nombre de valeurs. Analysez les valeurs selon le type, appelez à la fois Max et Sum avec le type approprié, et affichez les résultats.

    Pour une entrée entière, affichez :

    Max (int): [value]
    Sum (int): [value]

    Pour une entrée flottante, affichez avec une décimale :

    Max (float): [value]
    Sum (float): [value]

Les entrées suivantes seront fournies :

  • Ligne 1 : Indicateur de type (int ou float)
  • Ligne 2 : Nombre de valeurs (entier)
  • Lignes suivantes : Une valeur par ligne

Par exemple, avec :

int
4
15
8
23
11

Votre sortie devrait être :

Max (int): 23
Sum (int): 57

Et avec :

float
3
4.5
9.2
6.8

Votre sortie devrait être :

Max (float): 9.2
Sum (float): 20.5

Remarquez comment les mêmes fonctions génériques gèrent à la fois les données entières et à virgule flottante — le paramètre de type [T int | float64] permet au compilateur de générer un code de type sécurisé (type-safe) pour chaque utilisation alors que vous n'écrivez la logique qu'une seule fois.

Aide-mémoire

Les génériques vous permettent d'écrire du code réutilisable qui fonctionne avec plusieurs types tout en maintenant la sécurité des types. Avant Go 1.18, vous aviez besoin de fonctions distinctes pour chaque type ou vous utilisiez l'interface vide avec des assertions de type.

Les paramètres de type sont déclarés entre crochets et spécifient quels types une fonction générique peut accepter :

func Min[T int | float64](values []T) T {
    min := values[0]
    for _, v := range values {
        if v < min {
            min = v
        }
    }
    return min
}

La syntaxe [T int | float64] déclare un paramètre de type T qui peut être soit int soit float64. L'opérateur barre verticale | liste les types autorisés.

Vous pouvez appeler des fonctions génériques avec différents types :

Min([]int{3, 1, 2})           // Works with int
Min([]float64{3.5, 1.2})      // Works with float64

Le compilateur effectue la vérification des types au moment de la compilation, offrant une sécurité sans surcoût à l'exécution.

Essayez vous-même

package main

import (
	"bufio"
	"fmt"
	"os"
	"strconv"
	"strings"
)

func main() {
	reader := bufio.NewReader(os.Stdin)

	// Lire l'indicateur de type
	typeStr, _ := reader.ReadString('\n')
	typeStr = strings.TrimSpace(typeStr)

	// Lire le nombre d'éléments
	countStr, _ := reader.ReadString('\n')
	count, _ := strconv.Atoi(strings.TrimSpace(countStr))

	if typeStr == "int" {
		// Lire les valeurs entières
		values := make([]int, count)
		for i := 0; i < count; i++ {
			line, _ := reader.ReadString('\n')
			values[i], _ = strconv.Atoi(strings.TrimSpace(line))
		}

		// TODO: Appeler Max et Sum avec la slice d'entiers
		// TODO: Afficher les résultats au format "Max (int): [value]" et "Sum (int): [value]"

	} else if typeStr == "float" {
		// Lire les valeurs flottantes
		values := make([]float64, count)
		for i := 0; i < count; i++ {
			line, _ := reader.ReadString('\n')
			values[i], _ = strconv.ParseFloat(strings.TrimSpace(line), 64)
		}

		// TODO: Appeler Max et Sum avec la slice de float64
		// TODO: Afficher les résultats avec une décimale au format "Max (float): [value]" et "Sum (float): [value]"
	}
}
quiz iconTestez-vous

Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.

Toutes les leçons de Programmation Orientée Objet