Алгебраические типы: моделируем домен
Знакомимся с самым выразительным инструментом OCaml для моделирования данных — алгебраическими (вариантными) типами.
Вариантный тип (sum type) описывает значение, которое может быть одной из нескольких альтернатив, каждая со своим набором данных.
Записи отвечают на вопрос «И то, и то» (имя и возраст), а вариантные типы — на вопрос «Или то, или то» (платёж или наличными, или картой). Вместе они дают алгебраические типы данных — способ описать домен так точно, что многие ошибки становятся невыразимыми.
Простые варианты
type color = Red | Green | Blue
let to_hex = function
| Red -> "#FF0000"
| Green -> "#00FF00"
| Blue -> "#0000FF"
Имена вариантов (конструкторы) пишутся с заглавной буквы. Здесь function — сокращение для fun x -> match x with ....
Варианты с данными
type shape =
| Circle of float (* радиус *)
| Rectangle of float * float (* ширина, высота *)
| Triangle of float * float (* основание, высота *)
let area = function
| Circle r -> 3.14159 *. r *. r
| Rectangle (w, h) -> w *. h
| Triangle (b, h) -> 0.5 *. b *. h
let s = area (Rectangle (3.0, 4.0)) (* 12. *)
Каждый конструктор хранит свой набор полей. Невозможно случайно обратиться к «ширине круга» — у круга её просто нет в типе.
Моделирование домена
Алгебраические типы — это способ «сделать недопустимые состояния невыразимыми»:
(* Хорошо: каждый способ несёт ровно свои данные *)
type payment =
| Cash
| Card of string (* номер карты *)
| Transfer of { iban : string; bic : string }
Нельзя создать «наличную оплату с номером карты» — такого значения попросту не существует. Это прагматичная сила OCaml: домен описывается типами, а компилятор следит за согласованностью.
Рекурсивные типы
type tree =
| Leaf
| Node of int * tree * tree
let rec sum = function
| Leaf -> 0
| Node (v, l, r) -> v + sum l + sum r
Как работает под капотом
Варианты без данных компилятор представляет просто целыми числами. Варианты с данными — как блок в куче с тегом, указывающим, какой это конструктор, и полями. При сопоставлении компилятор смотрит на тег и выбирает ветку. Поскольку набор конструкторов известен и закрыт, компилятор может проверить исчерпывающность: если вы забыли вариант, он выдаст предупреждение. Это делает добавление нового состояния безопасным — компилятор укажет места для обновления.
Частые ошибки
- Имя конструктора со строчной буквы. Конструкторы обязаны начинаться с заглавной.
- Моделировать варианты строками-флагами. Это теряет проверки компилятора.
- Игнорировать предупреждение о неисчерпывающем match. Это сигнал о реальной дыре в логике.
Итоги
- Вариантный тип описывает «или то, или то»; конструкторы пишутся с заглавной и могут нести данные.
- Алгебраические типы делают недопустимые состояния невыразимыми — точное моделирование домена.
- Закрытость вариантов даёт проверку исчерпывающности при сопоставлении с образцом.