Какие виды объединений (union types) существуют в системах типов?

Ответ

В статически типизированных языках (TypeScript, Rust, Haskell) union types позволяют определять переменные, которые могут хранить значения разных типов.

Основные виды:

  1. Простое объединение (Untagged Union) Переменная может быть одного из нескольких типов, но у системы типов нет информации для их автоматического различения.

    let id: string | number;
    id = "abc123"; // OK
    id = 123;       // OK
    // Для использования нужна проверка типа (type guard)
    if (typeof id === 'string') {
      console.log(id.toUpperCase());
    }
  2. Размеченное объединение (Tagged/Discriminated Union) Каждый вариант объединения имеет общее поле-дискриминатор (тег), что позволяет системе типов однозначно определить тип.

    type Shape =
      | { kind: 'circle'; radius: number }    // Тег 'circle'
      | { kind: 'square'; side: number }      // Тег 'square'
      | { kind: 'rectangle'; width: number; height: number };
    
    function getArea(shape: Shape): number {
      // TypeScript понимает тип по полю 'kind'
      switch (shape.kind) {
        case 'circle':
          return Math.PI * shape.radius ** 2; // Доступно shape.radius
        case 'square':
          return shape.side ** 2; // Доступно shape.side
        case 'rectangle':
          return shape.width * shape.height;
      }
    }

Практическая польза: Размеченные объединения исключают ошибки времени выполнения, обеспечивая полную проверку типов на этапе компиляции и устраняя необходимость в приведениях типов (type casting).

Ответ 18+ 🔞

Да ты посмотри, какие штуки придумали, блядь! Union types, ёпта! Это ж как в жизни: ну вот, допустим, у тебя в кармане может лежать либо ключ от хаты, либо зажигалка, либо, нахуй, забытая жвачка. И ты, такой, достаёшь — а там хрен пойми что. Вот это и есть простое объединение, untagged, блядь.

let id: string | number;
id = "abc123"; // О, строка, окей
id = 123;       // Теперь число, тоже норм
// А вот чтобы понять, что в руках держишь — надо щупать, блядь, проверять!
if (typeof id === 'string') {
  console.log(id.toUpperCase()); // Только тут уже точно знаешь — это строка, можно и покричать.
}

Но это же, сука, неудобно! Каждый раз проверять, что за хуйня у тебя в переменной? Надоест, блядь. Поэтому умные дядьки придумали размеченные объединения, tagged unions. Это уже высший пилотаж, ёбана!

Суть проста, как три копейки: каждый вариант объединения идёт с бирочкой, с тегом. Как в гардеробе — висит пальто, на крючке бирка «Петров». И ты сразу знаешь, чё это.

type Shape =
  | { kind: 'circle'; radius: number }    // Бирочка 'circle', внутри радиус
  | { kind: 'square'; side: number }      // Бирочка 'square', сторона
  | { kind: 'rectangle'; width: number; height: number }; // Ну ты понял, блядь

function getArea(shape: Shape): number {
  // TypeScript, он не дурак, он по бирочке 'kind' сразу соображает, что за фигура!
  switch (shape.kind) {
    case 'circle':
      return Math.PI * shape.radius ** 2; // И даёт доступ К ТОЛЬКО К РАДИУСУ, блядь! К side уже не полезет.
    case 'square':
      return shape.side ** 2;
    case 'rectangle':
      return shape.width * shape.height;
  }
}

И вся, блядь, практическая польза в чём? Да в том, что ошибки времени выполнения — это пиздец и боль. А с такими штуками компилятор тебе ещё до запуска программы ебальник набьёт, если ты где-то накосячил с типами. Никаких лишних проверок, никаких сомнительных приведений типов — чистая, ебать, математика и порядок. Красота, да и только!