Sobes.tech
Senior

Какво е сумата от типове и как да я реализираме в Go?

sobes.tech AI

Отговор от AI

Сумата на типове (или етикетирана обединение, дисжунктивна обединение) е тип данни, който може да приема стойност на една от няколко предварително дефинирани варианта. За разлика от произведението на типове (структури), където стойността съдържа всички полета едновременно, сумата на типове съдържа само една от възможните стойности.

В Go няма директна поддръжка за суми от типове като в езици като Haskell или Rust (enum). Сумата от типове може да бъде емулирана по няколко начина:

  1. Интерфейси и твърдение за тип (Type Assertion):

    Създайте интерфейс, който ще реализират всички възможни варианти. Стойността на сумата от типове ще представлява този интерфейс, а за определяне на конкретния вариант се използва твърдение за тип или type switch.

    package main
    
    import "fmt"
    
    // Shape - интерфейс, представляващ сумата от типове
    type Shape interface {
    	Area() float64
    }
    
    // Circle - един от вариантите
    type Circle struct {
    	Radius float64
    }
    
    func (c Circle) Area() float64 {
    	return 3.14 * c.Radius * c.Radius
    }
    
    // Rectangle - друг вариант
    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 {
    		// Използване на switch за тип за определяне на варианта
    		switch v := s.(type) {
    		case Circle:
    			fmt.Printf("Кръг с радиус %.2f, площ: %.2f\n", v.Radius, v.Area())
    		case Rectangle:
    			fmt.Printf("Правоъгълник с размери %.2f x %.2f, площ: %.2f\n", v.Width, v.Height, v.Area())
    		default:
    			fmt.Println("Непозната форма")
    		}
    	}
    }
    
  2. Структури с булеви флагове (рядко използвани):

    Структурата съдържа полета за всички възможни варианти и булеви флагове за указване кой вариант е активен. Това е небезопасно и трудно за поддръжка.

    package main
    
    import "fmt"
    
    type Result struct {
    	Value int  // Поле за успешен резултат
    	Err   error // Поле за грешка
    
    	IsValue bool // Флаг, който указва дали е активна стойността
    	IsErr   bool // Флаг, който указва дали е активна грешката
    }
    
    func Process(input int) Result {
    	if input > 0 {
    		return Result{Value: input * 2, IsValue: true}
    	}
    	return Result{Err: fmt.Errorf("отрицателно число: %d", input), IsErr: true}
    }
    
    func main() {
    	res1 := Process(10)
    	if res1.IsValue {
    		fmt.Printf("Резултат: %d\n", res1.Value)
    	} else if res1.IsErr {
    		fmt.Printf("Грешка: %v\n", res1.Err)
    	}
    
    	res2 := Process(-5)
    	if res2.IsValue {
    		fmt.Printf("Резултат: %d\n", res2.Value)
    	} else if res2.IsErr {
    		fmt.Printf("Грешка: %v\n", res2.Err)
    	}
    }
    
  3. Структури с нулева стойност за неактивни полета (често използвани за Optional/Result):

    Структурата съдържа полета за всички възможни варианти, но само едно от тях ще бъде ненулево. Работи добре за указатели или типове с ясно "нулево" значение.

    package main
    
    import "fmt"
    
    // Option - емулира Some/None от опционалния тип
    type Option struct {
    	Value *int // Ненулево, ако има стойност
    }
    
    // None създава нулева стойност Option
    func None() Option {
    	return Option{}
    }
    
    // Some създава Option със стойност
    func Some(val int) Option {
    	return Option{Value: &val}
    }
    
    func main() {
    	opt1 := Some(10)
    	if opt1.Value != nil {
    		fmt.Printf("Стойност: %d\n", *opt1.Value)
    	} else {
    		fmt.Println("Няма стойност")
    	}
    
    	opt2 := None()
    	if opt2.Value != nil {
    		fmt.Printf("Стойност: %d\n", *opt2.Value)
    	} else {
    		fmt.Println("Няма стойност")
    	}
    }
    

Най-идиоматичният и безопасен начин за емулиране на суми от типове в Go е използването на интерфейси и type switch. Това гарантира, че присъства само една от опциите и осигурява типова безопасност при работа с нея.