Czym jest suma typów i jak ją zaimplementować w Go?
sobes.tech AI
Odpowiedź od AI
Suma typów (lub unia etykietowana, rozłączna unia) to typ danych, który może przyjąć wartość jednej z kilku z góry zdefiniowanych opcji. W przeciwieństwie do iloczynu typów (struktur), gdzie wartość zawiera wszystkie pola jednocześnie, suma typów zawiera tylko jedną z możliwych wartości.
W Go nie ma bezpośredniego wsparcia dla sum typów jak w językach takich jak Haskell czy Rust (enum). Sumę typów można emulować na kilka sposobów:
-
Interfejsy i asercja typu (Type Assertion):
Utwórz interfejs, który implementują wszystkie możliwe warianty. Wartość sumy typów będzie tym interfejsem, a do określenia konkretnego wariantu używa się asercji typu lub
type switch.package main import "fmt" // Shape - interfejs reprezentujący sumę typów type Shape interface { Area() float64 } // Circle - jeden z wariantów type Circle struct { Radius float64 } func (c Circle) Area() float64 { return 3.14 * c.Radius * c.Radius } // Rectangle - inny wariant 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 { // Użycie switcha typu do określenia wariantu switch v := s.(type) { case Circle: fmt.Printf("Koło o promieniu %.2f, pole: %.2f\n", v.Radius, v.Area()) case Rectangle: fmt.Printf("Prostokąt o wymiarach %.2f x %.2f, pole: %.2f\n", v.Width, v.Height, v.Area()) default: fmt.Println("Nieznana forma") } } } -
Struktury z flagami booleanowymi (rzadko używane):
Struktura zawiera pola dla wszystkich możliwych wariantów i flagi booleanowe wskazujące, który wariant jest aktywny. Jest to niebezpieczne i trudne do utrzymania.
package main import "fmt" type Result struct { Value int // pole dla wyniku sukcesu Err error // pole dla błędu IsValue bool // flaga wskazująca, czy wartość jest aktywna IsErr bool // flaga wskazująca, czy błąd jest aktywny } func Process(input int) Result { if input > 0 { return Result{Value: input * 2, IsValue: true} } return Result{Err: fmt.Errorf("liczba ujemna: %d", input), IsErr: true} } func main() { res1 := Process(10) if res1.IsValue { fmt.Printf("Wynik: %d\n", res1.Value) } else if res1.IsErr { fmt.Printf("Błąd: %v\n", res1.Err) } res2 := Process(-5) if res2.IsValue { fmt.Printf("Wynik: %d\n", res2.Value) } else if res2.IsErr { fmt.Printf("Błąd: %v\n", res2.Err) } } -
Struktury z wartością null dla nieaktywnych pól (często używane dla Optional/Result):
Struktura zawiera pola dla wszystkich wariantów, ale tylko jedno z nich będzie nie null. Działa dobrze z wskaźnikami lub typami mającymi wyraźną "wartość null".
package main import "fmt" // Option - emulacja Some/None z typu opcjonalnego type Option struct { Value *int // nie null, jeśli jest wartość } // None tworzy nullową wartość Option func None() Option { return Option{} } // Some tworzy Option z wartością func Some(val int) Option { return Option{Value: &val} } func main() { opt1 := Some(10) if opt1.Value != nil { fmt.Printf("Wartość: %d\n", *opt1.Value) } else { fmt.Println("Brak wartości") } opt2 := None() if opt2.Value != nil { fmt.Printf("Wartość: %d\n", *opt2.Value) } else { fmt.Println("Brak wartości") } }
Najbardziej idiomatycznym i bezpiecznym sposobem emulacji sum typów w Go jest użycie interfejsów i type switch. To zapewnia, że tylko jedna z opcji jest obecna i gwarantuje bezpieczeństwo typów podczas pracy z nią.