Что такое pattern matching и где его можно применять?
Pattern Matching (сопоставление с образцом) в Elixir — это один из центральных механизмов языка, который позволяет не просто присваивать значения, а деконструировать, проверять структуру и извлекать данные из сложных структур в элегантной и выразительной форме. Это мощный инструмент, который заменяет традиционные if, switch, try-catch и многие другие конструкции, встречающиеся в императивных языках.
Основы сопоставления с образцом
В Elixir знак = — не оператор присваивания, как, например, в JavaScript или Python. Это оператор сопоставления: он проверяет, можно ли левую часть уравнять правой, и, если да, извлекает данные.
{:ok, result} = {:ok, 42} # result = 42
Если структура слева не совпадает со структурой справа, произойдёт ошибка сопоставления (MatchError):
{:ok, result} = {:error, "ошибка"} # Ошибка
Где применяется Pattern Matching
1. Присваивание переменных
\[a, b, c\] = \[1, 2, 3\]
\# a = 1, b = 2, c = 3
%{name: n} = %{name: "Alex", age: 32}
\# n = "Alex"
Позволяет извлекать значения из списков, кортежей, словарей, структур и других вложенных объектов.
2. В функциях (определение по шаблону)
Функции могут иметь разные варианты (клаузы) в зависимости от входных данных:
defmodule Math do
def double(0), do: 0
def double(x), do: x \* 2
end
При вызове Math.double(0) сработает первая функция, при Math.double(5) — вторая.
3. Сопоставление в case
case {:ok, 200} do
{:ok, code} -> "Успешно: #{code}"
{:error, \_} -> "Ошибка"
end
Очень удобно для обработки результатов, ошибок, структурированных данных.
4. Сопоставление в with
with {:ok, token} <- get_token(),
{:ok, user} <- get_user(token),
{:ok, result} <- do_something(user) do
result
else
{:error, reason} -> {:error, reason}
end
with позволяет последовательно обрабатывать цепочки действий, в каждой из которых ожидается определённая форма результата. Если хоть одно сопоставление не удаётся, выполнение переходит в else.
5. Сопоставление в for, Enum, Stream
Enum.map(\[{1, "a"}, {2, "b"}\], fn {num, str} ->
"#{num}-#{str}"
end)
Можно сразу распаковывать кортежи, списки, структуры, что повышает читаемость и упрощает код.
6. Сопоставление в receive (актерная модель)
receive do
{:msg, content} -> IO.puts("Сообщение: #{content}")
{:error, reason} -> IO.puts("Ошибка: #{reason}")
end
Внутри receive используется pattern matching для обработки сообщений, поступающих в процесс.
7. Guard-условия + Pattern Matching
Можно применять шаблоны в сочетании с ограничениями (when):
def process(0), do: :zero
def process(n) when is_integer(n) and n > 0, do: :positive
def process(n) when is_integer(n) and n < 0, do: :negative
Это особенно полезно для фильтрации значений и определения логики.
8. Работа с Map и Struct
defmodule User do
defstruct name: "", age: 0
end
%User{name: name} = %User{name: "John", age: 30}
\# name = "John"
Сопоставление можно использовать и со структурами, где тип структуры — часть шаблона.
Особенности
- Одноразовое сопоставление: переменная может быть сопоставлена один раз в выражении, но если она уже определена — повторное сопоставление проверяет совпадение:
x = 5
5 = x # OK
6 = x # MatchError
- ^ (пин-переменная): используется для фиксации значения уже существующей переменной:
x = 10
^x = 10 # OK
^x = 20 # MatchError
Ошибки при сопоставлении
Если шаблон слишком строгий и ожидает конкретный формат, но получает другой — произойдёт ошибка:
\[a, b\] = \[1\] # MatchError
%{id: x} = %{name: "Bob"} # MatchError
Поэтому сопоставление часто сочетается с case, with, try, чтобы обрабатывать исключения.
Применимость в реальных задачах
-
Обработка {:ok, val} / {:error, reason}
-
Извлечение значений из вложенных структур (JSON, API)
-
Упрощение ветвлений
-
Чтение и разбор файлов (например, CSV, JSON, XML)
-
Упрощение кода в микросервисах, работающих через сообщения и процессы
-
API-пайплайны, проверки и валидации данных