Sobes.tech
Senior

O que é a soma de tipos e como implementá-la em Go?

sobes.tech IA

Resposta da IA

A soma de tipos (ou união etiquetada, união disjuntiva) é um tipo de dados que pode assumir o valor de uma de várias opções predefinidas. Ao contrário do produto de tipos (estruturas), onde o valor contém todos os campos simultaneamente, a soma de tipos contém apenas um dos valores possíveis.

Em Go, não há suporte direto para somas de tipos como em linguagens como Haskell ou Rust (enum). A soma de tipos pode ser emulada de várias maneiras:

  1. Interfaces e afirmação de tipo (Type Assertion):

    Criar uma interface que todas as opções possíveis implementem. O valor da soma de tipos será essa interface, e para determinar a opção concreta, usa-se afirmação de tipo ou type switch.

    package main
    
    import "fmt"
    
    // Shape - interface que representa a soma de tipos
    type Shape interface {
    	Area() float64
    }
    
    // Circle - uma das opções
    type Circle struct {
    	Radius float64
    }
    
    func (c Circle) Area() float64 {
    	return 3.14 * c.Radius * c.Radius
    }
    
    // Rectangle - outra opção
    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 {
    		// Uso de type switch para determinar a opção
    		switch v := s.(type) {
    		case Circle:
    			fmt.Printf("Círculo com raio %.2f, área: %.2f\n", v.Radius, v.Area())
    		case Rectangle:
    			fmt.Printf("Retângulo com dimensões %.2f x %.2f, área: %.2f\n", v.Width, v.Height, v.Area())
    		default:
    			fmt.Println("Forma desconhecida")
    		}
    	}
    }
    
  2. Estruturas com flags booleanos (raramente usadas):

    A estrutura contém campos para todas as opções possíveis e flags booleanos para indicar qual opção está ativa. É inseguro e difícil de manter.

    package main
    
    import "fmt"
    
    type Result struct {
    	Value int  // Campo para resultado bem-sucedido
    	Err   error // Campo para erro
    
    	IsValue bool // Flag indicando se Value está ativo
    	IsErr   bool // Flag indicando se Err está ativo
    }
    
    func Process(input int) Result {
    	if input > 0 {
    		return Result{Value: input * 2, IsValue: true}
    	}
    	return Result{Err: fmt.Errorf("número negativo: %d", input), IsErr: true}
    }
    
    func main() {
    	res1 := Process(10)
    	if res1.IsValue {
    		fmt.Printf("Resultado: %d\n", res1.Value)
    	} else if res1.IsErr {
    		fmt.Printf("Erro: %v\n", res1.Err)
    	}
    
    	res2 := Process(-5)
    	if res2.IsValue {
    		fmt.Printf("Resultado: %d\n", res2.Value)
    	} else if res2.IsErr {
    		fmt.Printf("Erro: %v\n", res2.Err)
    	}
    }
    
  3. Estruturas com valor nulo para campos não ativos (frequentemente usadas para Optional/Result):

    A estrutura contém campos para todas as opções possíveis, mas apenas um deles será não nulo. Funciona bem com ponteiros ou tipos que têm um valor "nulo" bem definido.

    package main
    
    import "fmt"
    
    // Option - emulação de Some/None do tipo opcional
    type Option struct {
    	Value *int // Não nulo se houver valor
    }
    
    // None cria um valor Option nulo
    func None() Option {
    	return Option{}
    }
    
    // Some cria um Option com valor
    func Some(val int) Option {
    	return Option{Value: &val}
    }
    
    func main() {
    	opt1 := Some(10)
    	if opt1.Value != nil {
    		fmt.Printf("Valor: %d\n", *opt1.Value)
    	} else {
    		fmt.Println("Sem valor")
    	}
    
    	opt2 := None()
    	if opt2.Value != nil {
    		fmt.Printf("Valor: %d\n", *opt2.Value)
    	} else {
    		fmt.Println("Sem valor")
    	}
    }
    

A maneira mais idiomática e segura de emular somas de tipos em Go é usar interfaces e type switch. Isso garante que apenas uma das opções esteja presente e fornece segurança de tipo ao trabalhar com ela.