match: основы сопоставления с образцом

Осваиваем сердце OCaml — конструкцию match, которая одновременно разбирает структуру данных и извлекает из неё значения.

Сопоставление с образцом (pattern matching) — выбор ветки по форме значения с одновременным извлечением его частей в переменные.

Если в императивных языках разбор данных — это череда if/switch и ручное извлечение полей, то в OCaml это одна выразительная конструкция. match сравнивает значение с набором образцов и для подошедшего выполняет ветку, попутно связывая части значения с именами.

Базовый синтаксис

let describe n =
  match n with
  | 0 -> "ноль"
  | 1 -> "один"
  | _ -> "много"

Каждая ветка начинается с |, затем образец, стрелка -> и результат. Образец _ — «что угодно». Все ветки возвращают значения одного типа, ведь match — выражение.

Связывание переменных

type shape = Circle of float | Rect of float * float

let area s =
  match s with
  | Circle r -> 3.14159 *. r *. r
  | Rect (w, h) -> w *. h

В ветке Circle r имя r связывается с радиусом, в Rect (w, h)w и h с шириной и высотой. Образец делает извлечение сам.

Разбор кортежей и вложенность

let classify pair =
  match pair with
  | (0, 0) -> "начало координат"
  | (0, _) -> "на оси Y"
  | (_, 0) -> "на оси X"
  | (x, y) -> Printf.sprintf "точка (%d, %d)" x y

Образцы проверяются сверху вниз: первый подошедший выигрывает. Поэтому конкретные случаи ставят выше общих.

Сокращение function

let area = function
  | Circle r -> 3.14159 *. r *. r
  | Rect (w, h) -> w *. h

Это эквивалентно fun s -> match s with ..., просто короче.

Как работает под капотом

Компилятор не выполняет образцы наивно «по одному». Он строит дерево решений — оптимальную последовательность проверок, которая разбирает значение за минимальное число шагов, заглядывая в теги конструкторов. Кроме того, анализатор образцов проверяет два свойства: исчерпывающность (покрыты ли все случаи) и избыточность (нет ли недостижимых веток). Оба выдаются как предупреждения — бесплатная проверка логики.

Частые ошибки

  • Поставить общий образец _ слишком рано. Он поглотит последующие ветки.
  • Думать, что переменная в образце сравнивается со значением. Имя x связывается с любым значением, а не проверяет равенство.
  • Забыть | перед веткой. Первый | можно опустить, но единообразие читается лучше.

Итоги

  • match значение with | образец -> ... выбирает ветку по форме данных и связывает их части.
  • Образцы проверяются сверху вниз; _ ловит всё остальное и должен идти последним.
  • function — краткая запись функции, сразу делающей match по аргументу.
Проверьте себя
1. Что делает образец `Rect (w, h)` в ветке match?
AСравнивает значение с константами w и h
BСвязывает w и h с шириной и высотой прямоугольника
CСоздаёт новый Rect
DВызывает функцию Rect
2. В каком порядке проверяются ветки match?
AВ случайном
BСверху вниз, первый подошедший образец выигрывает
CСнизу вверх
DПо приоритету типов
3. Чем `function | ...` отличается от `match`?
AНичем по смыслу — это краткая запись fun x -> match x with ...
Bfunction работает только с числами
Cfunction не проверяет исчерпывающность
Dfunction изменяет значение