Sobes.tech
Senior

Qu'est-ce que la somme de types et comment la réaliser en Go?

sobes.tech IA

Réponse de l'IA

La somme de types (ou union étiquetée, union disjonctive) est un type de données qui peut prendre la valeur d'une de plusieurs options prédéfinies. Contrairement au produit de types (structures), où la valeur contient tous les champs simultanément, la somme de types ne contient qu'une seule des valeurs possibles.

En Go, il n'y a pas de support direct pour les sommes de types comme dans des langages comme Haskell ou Rust (enum). La somme de types peut être émulation de plusieurs manières :

  1. Interfaces et assertion de type (Type Assertion) :

    Créer une interface que toutes les options possibles implémentent. La valeur de la somme de types sera cette interface, et pour déterminer l'option concrète, on utilise l'assertion de type ou type switch.

    package main
    
    import "fmt"
    
    // Shape - interface représentant la somme de types
    type Shape interface {
    	Area() float64
    }
    
    // Circle - une des options
    type Circle struct {
    	Radius float64
    }
    
    func (c Circle) Area() float64 {
    	return 3.14 * c.Radius * c.Radius
    }
    
    // Rectangle - une autre option
    type Rectangle struct {
    	Width, Height float64
    }
    
    func (r Rectangle) Area() float64 {
    	return r.Width * r.Height
    }
    
    func main() {
    	shapes := []Shape{Circle{Radius: 5}, Rectangle{Width: 3, Height: 4}}
    
    	for _, s := range shapes {
    		// Utilisation de type switch pour déterminer l'option
    		switch v := s.(type) {
    		case Circle:
    			fmt.Printf("Cercle avec rayon %.2f, aire : %.2f\n", v.Radius, v.Area())
    		case Rectangle:
    			fmt.Printf("Rectangle avec dimensions %.2f x %.2f, aire : %.2f\n", v.Width, v.Height, v.Area())
    		default:
    			fmt.Println("Forme inconnue")
    		}
    	}
    }
    
  2. Structures avec drapeaux booléens (rarement utilisées) :

    La structure contient des champs pour toutes les options possibles et des drapeaux booléens pour indiquer quelle option est active. C'est dangereux et difficile à maintenir.

    package main
    
    import "fmt"
    
    type Result struct {
    	Value int  // Champ pour résultat réussi
    	Err   error // Champ pour erreur
    
    	IsValue bool // Drapeau indiquant si Value est actif
    	IsErr   bool // Drapeau indiquant si Err est actif
    }
    
    func Process(input int) Result {
    	if input > 0 {
    		return Result{Value: input * 2, IsValue: true}
    	}
    	return Result{Err: fmt.Errorf("nombre négatif : %d", input), IsErr: true}
    }
    
    func main() {
    	res1 := Process(10)
    	if res1.IsValue {
    		fmt.Printf("Résultat : %d\n", res1.Value)
    	} else if res1.IsErr {
    		fmt.Printf("Erreur : %v\n", res1.Err)
    	}
    
    	res2 := Process(-5)
    	if res2.IsValue {
    		fmt.Printf("Résultat : %d\n", res2.Value)
    	} else if res2.IsErr {
    		fmt.Printf("Erreur : %v\n", res2.Err)
    	}
    }
    
  3. Structures avec valeur nulle pour les champs non actifs (souvent utilisé pour Optional/Result) :

    La structure contient des champs pour toutes les options possibles, mais un seul d'entre eux sera non nul. Fonctionne bien avec des pointeurs ou des types ayant une valeur "nulle" claire.

    package main
    
    import "fmt"
    
    // Option - émulation de Some/None du type optionnel
    type Option struct {
    	Value *int // Non nul s'il y a une valeur
    }
    
    // None crée une valeur Option nulle
    func None() Option {
    	return Option{}
    }
    
    // Some crée une Option avec une valeur
    func Some(val int) Option {
    	return Option{Value: &val}
    }
    
    func main() {
    	opt1 := Some(10)
    	if opt1.Value != nil {
    		fmt.Printf("Valeur : %d\n", *opt1.Value)
    	} else {
    		fmt.Println("Pas de valeur")
    	}
    
    	opt2 := None()
    	if opt2.Value != nil {
    		fmt.Printf("Valeur : %d\n", *opt2.Value)
    	} else {
    		fmt.Println("Pas de valeur")
    	}
    }
    

La méthode la plus idiomatique et sûre pour émuler des sommes de types en Go est d'utiliser interfaces et type switch. Cela garantit qu'une seule option est présente et assure la sécurité de type lors de son utilisation.