Углубление · Алгебраические типы ← Глава VI
Углубление к главам I и VI

Алгебраические типы данных.

«Алгебраические» — буквально: типы можно складывать и умножать, и они образуют полукольцо. Произведение — это «И», сумма — «ИЛИ». Из этих двух операций собираются структуры, в которых недопустимые состояния просто нельзя выразить. Здесь алгебра из главы I встречается с функциональным программированием из главы VI.

A × Bпроизведение (И)
A + Bсумма (ИЛИ)
|·|кардинальность
TYPI + · × sum · product semiring A × B AB A + B типы образуют полукольцо
№ 00Идея

Типы можно считать

Тип — это множество возможных значений. У Bool их два, у единичного типа — одно, у пустого — ни одного. Раз так, типы можно складывать и умножать, как числа, а у составного типа — считать число обитателей. Эта «арифметика типов» и дала название: алгебраические типы данных.

Польза двойная. Во-первых, это прямой мост из главы об алгебре: типы образуют ту же структуру — полукольцо. Во-вторых, считая значения, инженер видит, какие состояния вообще возможны, — и может сделать недопустимые невыразимыми.

Только с алгеброй начинается строгое математическое учение. — Н. И. Лобачевский
×+
Две операции

Произведение — «И», сумма — «ИЛИ»

Всего две конструкции. Произведение объединяет типы союзом «и»: значение содержит A и B. Сумма — союзом «или»: значение есть A или B, с пометкой, что именно.

×Произведение (record/tuple)

Кортеж, запись, структура: A и B одновременно. Число значений перемножается: $|A\times B| = |A|\cdot|B|$.

+Сумма (variant/enum)

Тегированное объединение: A или B. Число значений складывается: $|A + B| = |A| + |B|$. Это Maybe, Either, Result.

В типизированных языках это пишется прямо:

-- сумма: значение — одно из вариантов
type Maybe a = Nothing | Just a -- 1 + a
type Either a b = Left a | Right b -- a + b

-- произведение: значение содержит оба поля
type Point = Point Float Float -- Float × Float
⊕⊗
Алгебра типов

Полукольцо — то самое, из главы I

Сумма и произведение типов подчиняются ровно тем же законам, что сложение и умножение чисел. С двумя особыми типами — пустым (0 значений) и единичным (1 значение) — типы образуют коммутативное полукольцо.

Единицы и поглощение — как в арифметике:

$A\times 1 \cong A,\qquad A + 0 \cong A,\qquad A\times 0 \cong 0$

И дистрибутивность — умножение раскрывается над суммой:

$A\times(B + C)\;\cong\;A\times B \,+\, A\times C$

Где $0$ — пустой тип (значений нет), $1$ — единичный (одно значение, как () или :ok). Изоморфизм $\cong$ означает «одинаковое число значений и взаимно-обратные преобразования». Посчитаем обитателей:

CARDINALISсчёт значений 0 · 1 · + · ×B^A Void= 0значений нет Unit ()= 1одно значение Bool= 1 + 1 = 2 Maybe a= 1 + a A → B= B^Aфункции — степень
Считая значения, видно: Bool — это «1 + 1», Maybe — «1 + a», а функции A→B — степень B^A. Алгебра буквальна.

Что это даёт практически: изоморфные типы взаимозаменяемы. Например, $\mathrm{Maybe}\,a \cong 1 + a$ — то же, что «либо ничего, либо значение». А распределительный закон подсказывает безопасные рефакторинги представления данных, не меняющие множество допустимых значений.

✓
Корректность по построению

Сделать недопустимое невыразимым

Главная сила суммы — моделировать состояния так, чтобы неверные комбинации нельзя было собрать. Вместо россыпи флагов — один тип-сумма, где каждый вариант осмыслен.

Три булевых поля дают $2\times2\times2 = 8$ комбинаций, из которых осмысленны три. Сумма-тип оставляет ровно допустимые состояния — остальные просто не существуют как значения. А исчерпывающий разбор (pattern matching) заставляет обработать каждый вариант: компилятор или линтер укажет на пропуск. Это и есть мост к главе о математически корректных программах — частный случай тотальности.

-- 3 состояния, а не 8 комбинаций флагов
type Conn = Idle | Connecting | Online Session
STATUS3 ≠ 8 только допустимыеexhaustive Idle Connecting Online 8 комбинаций флагов → невозможны
Сумма оставляет только осмысленные состояния; недопустимые не выразить.

В Elixir статических сумм-типов нет, но идиома та же: тегированные кортежи как сумма, структуры как произведение, и исчерпывающий case:

# сумма — теговые кортежи; произведение — структура
case result do
  {:ok, value} -> handle(value)
  {:error, reason} -> log(reason)
end
↺
Рекурсивные типы

Списки и деревья — уравнения над типами

Если тип ссылается на себя, получается рекурсивная структура. Её алгебра — это уравнение, решение которого — бесконечное семейство значений.

Список — это «пусто или голова и хвост-список»: сумма единицы и произведения. Алгебраически $L \cong 1 + a\times L$:

type List a = Nil | Cons a (List a) -- L = 1 + a × L
type Tree a = Leaf | Node (Tree a) a (Tree a)
RECURSIOL = 1 + a×L Cons · Cons · Nil∞ Cons a Cons a Cons a Nil
Список — цепочка Cons, замкнутая на Nil. Дерево — та же идея с ветвлением; отсюда же — дерево надзора агентов.

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

№ 06Итог

Алгебра, ставшая типами

Типы складываются и умножаются, образуют полукольцо с нулём и единицей, подчиняются дистрибутивности, а функции дают степени. Считая обитателей, инженер делает недопустимые состояния невыразимыми; исчерпывающий разбор гарантирует, что учтён каждый вариант; рекурсивные типы описывают списки и деревья как уравнения. Алгебра из главы I буквально живёт в типах из главы VI.

Это естественный вход в следующую главу: когда состояния честно смоделированы, а разбор исчерпывающ, программа становится корректной по построению. О полной корректности — типах, тотальности и доказательствах — глава VII.

Изоморфные типы — это одно и то же, записанное по-разному. — принцип алгебры типов
№ 07Опоры и связи

Куда дальше