Что такое тип-сумма (sum type) в Go и как его эмулировать?

Ответ

В Go нет встроенной поддержки тип-сумм (также известных как алгебраические типы данных), но этот паттерн можно эффективно эмулировать с помощью интерфейсов и неэкспортируемого метода-маркера.

Тип-сумма — это тип данных, который может принимать значение одного из нескольких определенных, заранее известных типов.

Пример эмуляции в Go

Представим, что у нас есть фигура, которая может быть либо кругом, либо прямоугольником. Мы можем смоделировать это так:

package shapes

import "math"

// Shape — это наш тип-сумма. Только типы из этого пакета могут его реализовать.
type Shape interface {
    // isShape — неэкспортируемый метод-маркер.
    // Он не дает типам из других пакетов реализовать этот интерфейс.
    isShape()
}

type Circle struct { 
    Radius float64 
}
func (c Circle) isShape() {}

type Rectangle struct { 
    Width, Height float64 
}
func (r Rectangle) isShape() {}

// Area безопасно работает с любым типом, реализующим Shape
func Area(s Shape) float64 {
    switch v := s.(type) {
    case Circle:
        return math.Pi * v.Radius * v.Radius
    case Rectangle:
        return v.Width * v.Height
    default:
        // Этот случай теоретически недостижим, если мы контролируем пакет
        panic("неизвестный тип фигуры")
    }
}

Как это работает:

  1. Закрытый интерфейс: Мы создаем интерфейс Shape с неэкспортируемым методом (isShape). Поскольку метод начинается с маленькой буквы, его могут реализовать только типы, объявленные в том же пакете (shapes). Это эффективно "закрывает" набор возможных типов.
  2. Реализации: Каждый конкретный тип (Circle, Rectangle) реализует этот интерфейс, предоставляя пустую реализацию метода-маркера.
  3. Type Switch: При обработке значения типа Shape используется type switch для безопасного определения конкретного типа и выполнения соответствующей логики. Это гарантирует, что мы обработаем все возможные варианты, определенные в нашем пакете.