Паттерн-матчинг: основы
Паттерн-матчинг — это не синтаксический сахар, а способ мышления в Elixir. Он заменяет половину привычных условий и распаковок.
Вместо «достань поле и проверь» вы описываете форму данных, и язык сам её разбирает или сообщает о несовпадении.
Слева от = вы пишете шаблон — форму, под которую должна подойти правая часть. Совпало — переменные привязались; нет — MatchError.
{:ok, value} = {:ok, 42} # value => 42
[first | rest] = [1, 2, 3] # first => 1, rest => [2, 3]
%{name: n} = %{name: "Ann", age: 30} # n => "Ann"
{:ok, value} = {:error, :boom}
# ** (MatchError) no match of right hand side value: {:error, :boom}
Это и есть идиома обработки результатов: функция вернула {:ok, data} или {:error, reason}, и вы распаковываете нужную ветку.
[head | tail] = [10, 20, 30]
head # => 10
tail # => [20, 30]
# Игнорируем ненужное через _
{_, _, важное} = {1, 2, 3}
важное # => 3
Как работает под капотом (BEAM)
Матчинг компилируется в эффективный код: BEAM сравнивает структуру шаблона с данными «по форме» — проверяет теги кортежей, размеры, ключи map'ов — и привязывает переменные за один проход. Для списков это особенно дёшево: [head | tail] просто берёт указатели на голову и хвост связной структуры, без копирования. Несовпадение шаблона — это нормальный, ожидаемый исход, а не дорогое исключение: на нём построены ветвления в case и выбор функций по сигнатуре.
Шаблон: {:ok, value}
Данные: {:ok, 42}
| |
тег ок? привязать value=42
=> совпало
Та же идея на Python ▶
Современный Python умеет похожее — распаковку и structural pattern matching.
# Деструктуризация как {:ok, value} = ...
status, value = ("ok", 42)
print(status, value) # ok 42
# [head | tail]
head, *tail = [1, 2, 3]
print(head, tail) # 1 [2, 3]
# %{name: n} = ...
data = {"name": "Ann", "age": 30}
name = data["name"]
print(name) # Ann
# structural matching (Python 3.10+) — ближе всего к Elixir
def describe(pair):
match pair:
case ("ok", v): return f"успех: {v}"
case ("error", r): return f"ошибка: {r}"
print(describe(("ok", 42))) # успех: 42
print(describe(("error", "boom"))) # ошибка: boom
Частые ошибки
- Матчить весь map, а не часть. В Elixir
%{name: n}матчит, даже если в map есть другие ключи — это частичный матч, и это нормально. - Забыть про MatchError. Если форма не совпала, вы получите исключение — иногда это желаемо (fail fast), иногда нужен
case. - Путать
=и==. Первое — матч и привязка, второе — проверка равенства, возвращающая булево.
Best practices
- Распаковывайте данные матчингом прямо в аргументах функций и в
case, а не геттерами. - Используйте
_и_имядля игнорируемых частей — это документирует намерение. - Позволяйте MatchError падать там, где «несовпадение = баг»: это и есть «let it crash».
Итог. Паттерн-матчинг — это распаковка, ветвление и валидация в одном операторе. Освоив его, вы перестаёте писать ручные проверки полей. Дальше расширим его охранными выражениями и применим к выбору функций.
Матчинг как валидация формы
Отдельно стоит подчеркнуть роль матчинга как встроенной проверки контракта. Когда вы пишете {:ok, data} = fetch(), вы не просто распаковываете результат — вы заявляете «я ожидаю успех, и если его нет, это баг, пусть процесс упадёт». Это идиома fail-fast: ошибки не маскируются, а проявляются ровно в точке нарушения ожидания, с понятным стектрейсом. В мире, где есть супервизоры, такое падение безопасно и даже полезно.
Когда же несовпадение — это штатный вариант (например, элемент может быть в кэше, а может и нет), матч заворачивают в case и явно описывают обе ветки. Граница между «пусть падает» и «обработаю обе ветки» — важное проектное решение: первое для инвариантов, которые обязаны выполняться, второе для ожидаемой вариативности. Привыкайте задавать себе вопрос «несовпадение здесь — это баг или нормальный случай?» — ответ подскажет, нужен ли вам жёсткий матч или ветвление.