Сопоставление с образцом (pattern matching)
Механизм, который в Erlang встречается буквально на каждом шагу.
Сопоставление с образцом — проверка структуры значения на соответствие шаблону с одновременным извлечением частей в переменные.
Если в Erlang есть одна идея, которую нужно понять по-настоящему, — это pattern matching. Он встречается в присваивании, в аргументах функций, в разборе сообщений, в конструкции case. Освоив его, вы начнёте «думать на Erlang». Многие приходят в язык из мира цепочек условий и первое время по инерции пытаются написать всё через них. Но в Erlang большая часть таких проверок исчезает: вы описываете, как должны выглядеть данные, и язык сам решает, подходят они или нет. Это смещает мышление от «как мне вытащить значение» к «какую форму я ожидаю».
Важно сразу осознать ещё одну особенность: знак = в Erlang — это не присваивание, а оператор сопоставления. Слева стоит шаблон, справа — значение, и язык пытается «подогнать» одно под другое. Несвязанная переменная в шаблоне связывается с соответствующей частью значения, а уже связанная переменная или литерал проверяются на равенство. Поэтому выражение, похожее на обычное присваивание, на деле может как извлечь данные, так и упасть с ошибкой, если форма не совпала.
Сопоставление вместо проверок
Вместо того чтобы спрашивать «а это кортеж из двух элементов? а первый — атом ok?», вы пишете шаблон, который описывает ожидаемую форму, и Erlang сам проверяет и распаковывает. Представьте, что вы получаете посылку и хотите убедиться, что внутри лежит именно то, что заказывали, а заодно сразу разложить содержимое по полкам. В императивном языке вы бы открыли коробку, проверили каждый предмет отдельным условием, и только потом разложили. В Erlang вы заранее рисуете «трафарет» того, что ожидаете увидеть, прикладываете его к посылке — и если всё совпало, нужные предметы автоматически оказываются в подписанных ячейках-переменных.
Такой подход не только короче, но и надёжнее. Описывая форму данных явно, вы превращаете молчаливое предположение в проверяемое утверждение: если придёт значение неожиданной формы, программа остановится ровно там, где допущение нарушилось, а не продолжит работать на испорченных данных. В философии Erlang это достоинство — лучше быстро и громко упасть, чем тихо распространять ошибку дальше по системе.
{ok, Value} = {ok, 42}.
% Value связывается с 42
{X, Y} = {10, 20}.
% X = 10, Y = 20
[First | Rest] = [1, 2, 3].
% First = 1, Rest = [2, 3]
Если форма не совпадает — будет ошибка no match. Это не баг, а способ заявить «я ожидаю именно такие данные». Например, попытка сопоставить {ok, Value} со значением {error, timeout} завершится крахом сопоставления: первый элемент кортежа — атом error, а шаблон требует атом ok. Это удобно использовать как встроенную проверку: если функция по контракту обязана вернуть успешный результат, вы пишете шаблон с ok, и любое отклонение от контракта немедленно проявит себя.
Обратите внимание и на то, что одно сопоставление проверяет несколько уровней вложенности сразу. Шаблон {user, {name, Name}, Age} за один проход убедится, что значение — кортеж из трёх элементов с вложенной парой name, и вытащит и имя, и возраст. Ручная навигация по структуре не нужна — распаковка происходит сама.
Переменная-заполнитель
Иногда часть значения нам не нужна. Символ подчёркивания _ — «пустышка», которая совпадает с чем угодно и ничего не связывает. Это не просто способ сэкономить имя переменной: заполнитель ясно сообщает читателю кода «эта часть данных мне неинтересна, и я сознательно её игнорирую». Когда вы возвращаетесь к своему коду спустя месяцы или его читает коллега, такой явный пропуск экономит время на догадки.
Существует и промежуточный вариант — переменная, имя которой начинается с подчёркивания, например _Name. Она ведёт себя как обычная связанная переменная, но компилятор не предупреждает, если вы её не используете. Это удобное самодокументирование: вы подсказываете читателю, что именно лежит в этой части значения, не вызывая предупреждений о неиспользуемой переменной.
{_, _, Third} = {a, b, c}.
% Third = c, первые два проигнорированы
[_ | Tail] = [10, 20, 30].
% Tail = [20, 30]
Конструкция case
Когда нужно выбрать ветку по форме значения, используют case. Он перебирает шаблоны сверху вниз и выполняет первую подошедшую ветку. По духу это похоже на оператор множественного ветвления из других языков, но мощнее: ветка выбирается не по равенству одному числу, а по совпадению со всей структурой значения, включая вложенные кортежи и списки. Это позволяет в одном месте аккуратно разобрать все возможные исходы операции — успех, разные виды ошибок, особый случай — и для каждого написать свою реакцию.
Типичный сценарий — обработка результата функции, которая по соглашению возвращает либо {ok, Данные}, либо {error, Причина}. Такой кортеж-результат — устоявшаяся идиома Erlang, и case разбирает его естественно: одна ветка достаёт данные успеха, другая — причину ошибки.
describe(Value) ->
case Value of
{ok, Data} -> {success, Data};
{error, Reason} -> {failure, Reason};
_ -> unknown
end.
Обратите внимание: ветки разделяются точкой с запятой, а весь блок закрывается словом end. Это частый источник опечаток у новичков: внутри case между ветками ставится точка с запятой, а вот точка завершила бы всё выражение раньше времени и сломала бы синтаксис. Полезно держать в голове общее правило Erlang: точка с запятой разделяет альтернативы (ветки, клаузы), запятая — последовательные шаги, а точка ставит финальную границу. Освоив эту троицу знаков, вы перестанете спотыкаться о пунктуацию.
Ещё одна тонкость: всё выражение case само возвращает значение — то, которое вычислила выбранная ветка. Поэтому результат case можно сразу связать с переменной или передать дальше, не заводя временных хранилищ. Это отражает функциональную природу языка, где почти любая конструкция является выражением со значением, а не «командой», выполняемой ради побочного эффекта.
Связанная переменная в шаблоне
Если переменная уже связана, в шаблоне она работает как проверка на равенство. Это прямое следствие того, что = — оператор сопоставления, а не присваивания. Раз переменная уже несёт значение, переприсвоить её нельзя; единственное осмысленное действие — сравнить то, что в ней лежит, с тем, что предлагает правая сторона. Если значения совпали, сопоставление успешно и ничего не меняется; если нет — крах сопоставления.
Это свойство часто используют как изящную замену явной проверки на равенство: вместо отдельного условия «а равно ли пришедшее значение ожидаемому» вы подставляете уже связанную переменную прямо в шаблон, и язык сравнивает за вас.
Expected = 5,
case get_value() of
Expected -> "то самое значение";
_ -> "другое значение"
end.
Где сопоставление встречается ещё
Стоит подчеркнуть, что case — лишь одно из мест, где живёт pattern matching. Та же механика работает в заголовках клауз функций и при разборе входящих сообщений в блоке receive, который мы изучим в разделе про процессы. Освоив сопоставление один раз, вы получаете единый инструмент, пронизывающий весь язык от присваивания до конкурентного программирования.
Как работает под капотом
Сопоставление с образцом компилируется в эффективное дерево решений: BEAM не перебирает варианты «в лоб», а строит оптимальную последовательность проверок типа и формы. Поэтому case с десятком веток работает быстро. Важно: сопоставление идёт сверху вниз и слева направо, первый подошедший шаблон выигрывает — порядок веток имеет значение. Компилятор анализирует все шаблоны разом и старается не проверять одно и то же дважды: например, если несколько веток начинаются с кортежа из двух элементов, форма проверяется один раз, а дальше различие идёт по содержимому.
Понимание роли порядка веток уберегает от коварных ошибок. Если поставить слишком общий шаблон выше конкретного, общий перехватит значение первым, и конкретная ветка никогда не выполнится. Поэтому ветки располагают от частного к общему, а запасную ветку с заполнителем держат в самом низу.
Частые ошибки
- Ставить
_, когда значение нужно. Заполнитель ничего не связывает — обратиться к нему нельзя. - Забыть ветку
_вcase. Если ни один шаблон не подошёл — ошибкаcase_clause. - Думать, что новая переменная в шаблоне сравнивается. Несвязанная переменная связывается; сравнивается только уже связанная.
Итоги
- Pattern matching проверяет форму значения и одновременно распаковывает части.
_— заполнитель, совпадающий с чем угодно и ничего не связывающий.case ... of ... endвыбирает первую подходящую ветку сверху вниз.- Связанная переменная в шаблоне работает как проверка на равенство.