Что такое тип-сумма (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 для безопасного определения конкретного типа и выполнения соответствующей логики. Это гарантирует, что мы обработаем все возможные варианты, определенные в нашем пакете.

Ответ 18+ 🔞

А, блядь, вот смотри, в 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 — функция, которая уже может спокойно работать с любой фигурой.
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). Таким образом мы и закрываем набор возможных вариантов, чтоб никакой левый Triangle с улицы не подсунули.
  2. Легальные члены клуба: Каждый конкретный тип (Circle, Rectangle) тихо-мирно реализует этот интерфейс, делая пустую заглушку для метода-маркера. Всё, пропуск получен.
  3. Волшебный свитч: Когда нам в функцию Area прилетает что-то типа Shape, мы делаем type switch. Это как тактильный осмотр в темноте: нащупал круг — считай площадь круга, нащупал прямоугольник — считай прямоугольник. И мы уверены, что других, неучтённых хуйней тут быть не может (если, конечно, не накосячили в самом пакете). Дефолтная ветка — это просто паранойя на максималках.

Вот и вся магия, блядь. Никаких вам волшебных enum'ов, всё на честном слове и интерфейсах. Зато работает, ёпта.