Исчерпывающность и сторожевые условия when

Используем способность компилятора проверять полноту разбора и уточняем образцы дополнительными условиями.

Проверка исчерпывающности — анализ компилятора, который предупреждает, если в match не покрыты все возможные формы значения.

Одно из самых ценных свойств OCaml: компилятор знает все возможные варианты вашего типа и проверяет, что вы их все обработали. Если добавить в тип новый конструктор, компилятор укажет каждое место, где про него забыли. Это превращает рефакторинг из источника багов в управляемый процесс.

Предупреждение о неполноте

type signal = Red | Yellow | Green

(* забыли Yellow *)
let action = function
  | Red -> "стой"
  | Green -> "иди"
Warning 8: this pattern-matching is not exhaustive.
Here is an example of a case that is not matched:
Yellow

Компилятор приводит конкретный пример непокрытого случая. Игнорировать опасно: при попадании Yellow в рантайме программа бросит Match_failure.

Сторожевые условия when

let categorize n =
  match n with
  | 0 -> "ноль"
  | n when n < 0 -> "отрицательное"
  | n when n > 100 -> "большое"
  | _ -> "обычное"

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

Объединение образцов через |

let is_vowel c =
  match c with
  | 'a' | 'e' | 'i' | 'o' | 'u' -> true
  | _ -> false

Образцы, объединённые через |, должны связывать одни и те же переменные (или никаких).

Связывание целого через as

let dedup_head = function
  | (x :: _) as lst when x = 0 -> lst
  | lst -> lst

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

Анализ исчерпывающности построен на том, что вариантные типы закрыты: компилятор знает полный список конструкторов. Для целых и строк исчерпывающим считается только наличие _ или переменной. Сторожевое условие when компилятор намеренно исключает из анализа: разрешимость произвольного булева выражения неразрешима в общем случае, поэтому язык консервативно требует запасной ветки.

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

  • Игнорировать Warning 8. Неполный match — потенциальный Match_failure. Многие проекты включают «warnings as errors».
  • Ждать, что when учтётся в исчерпывающности. Не учтётся; добавляйте запасной _.
  • Разные переменные в ветвях |. Объединённые образцы должны связывать одинаковый набор имён.

Итоги

  • Компилятор проверяет исчерпывающность и показывает пример непокрытого случая.
  • when добавляет условие к образцу, но не учитывается в проверке полноты.
  • | объединяет образцы с общим результатом; as именует сопоставленный фрагмент целиком.
Проверьте себя
1. Что покажет компилятор при неполном match по вариантному типу?
AНичего
BПредупреждение об исчерпывающности с примером непокрытого случая
CОшибку компиляции всегда
DЗамедлит программу
2. Почему после веток с `when` обычно нужен запасной образец `_`?
AТак требует синтаксис всегда
BКомпилятор не анализирует условия when и считает покрытие неполным
Cwhen замедляет match
Dwhen запрещает другие ветки
3. Что делает образец `'a' | 'e' | 'i' -> true`?
AСоздаёт список символов
BСрабатывает, если значение равно любому из перечисленных символов
CСравнивает с переменными a, e, i
DВызывает ошибку