Сопоставление паттерна и терма — одна из ключевых, фундаментальных особенностей Эрланга. То же: pattern matching.
Сопоставление приводит к двум результатам:
Сопоставление применяется для следующих целей:
Оператором сопоставления является знак равенства =
(но сопоставление может делаться и без этого оператора). Простой пример сопоставления:
1> X = 7.
Как мы видим по 1>
, это пример для оболочки. Раз это первая команда в оболочке, значит, до этого мы не успели привязать к X какое-либо значение. Значит, X и 7 подходят друг другу (сопоставление прошло успешно). И после этого, значит, к иксу будет привязано значение 7.
Переменная в Эрланге бывает в двух состояниях:
Привязывание значения к переменной происходит всегда в результате сопоставления. Когда мы видим выражение вроде X = 7
, то тут сопоставление. Терм в правой стороне (7) сопоставляется с паттерном в левой стороне (X).
Сначала проверяется, подходят ли друг другу 7 и X. Тут может быть три варианта:
Проверяем:
1> X = 7.
7
2> X = 7.
7
3> Y = 8.
8
4> Y = 7.
** exception error: no match of right hand side value 7
Сопоставления можно очень эффективно использовать в обработчике списка.
В некотором смысле правая сторона сопоставления важнее. X = 7
или какое-либо иное сопоставление это выражение, которое высчитывается и возвращает то или иное значение (терм). Поэтому допустима такая запись: Y = X = 8
. X = 8
превращается в значение 8, которое и участвует в следующем сопоставлении Y = 8
.
Но что вернёт выражение _ = 3.14
? К специальной переменной _ никакие значения не могут быть привязаны. Если из выражения будет возвращено 3.14, это будет хорошо. А если _, то будет ошибка. Это легко проверить:
2> _ = 3.14.
3.14
Было возвращено 3.14, потому что правая сторона как бы главнее, она определяет значение выражения сопоставления в целом. Можно, кстати, и такую длинную цепочку соорудить. Пусть это бессмысленно, но работает:
1> X = _ = _ = _ = _ = 777.
777
2> X.
777
Следует не забывать, что паттерны могут быть очень и очень сложными. Можно придумать сколь угодно “мохнатый” терм, следовательно, к нему можно придумать и сколь угодно “мохнатый” паттерн.
1> X = [{age,18},["red","blue","green"],fun(Y) -> 2*Y end,3.14].
[{age,18},
["red","blue","green"],
#Fun<erl_eval.42.3316493>,3.14]
2> Z = age.
age
3> [{Z,Vozrast},[Word1,Word2,Word3],Ano,3.14] = X.
[{age,18},
["red","blue","green"],
#Fun<erl_eval.42.3316493>,3.14]
4> Vozrast.
18
5> Word1.
"red"
6> [{Z,Vozrast},[Word1,Word2,Word3],Ano,3.15] = X.
** exception error: no match of right hand side value
[{age,18},
["red","blue","green"],
#Fun<erl_eval.42.3316493>,3.14]
Сначала мы создаём не самый простой терм: список, в который входит кортеж с двумя значениями, список — с тремя, затем фунтерм, а затем число 3.14. Получившееся значение привязывается к переменной X. Оболочка нам помогает и выдаёт обратную связь — значение, как она его видит.
Затем мы сопоставляем Z и атом age. Можно было бы, конечно, этого и не делать, но мне захотелось, чтобы в следующем сложном паттерне (шаг 3) была хотя бы одна переменная, к которой уже привязано какое-то значение.
И вот мы сопоставляем сложный паттерн с переменной X. Сопоставление проходит успешно, потому что никаких противоречий в ходе него выявлено не было: и структура совершенно идентичная, и не было такого, что сопоставляются две неравные друг другу вещи. Например, вместо 3.14 могло бы быть 3.15, а вместо атома age атом vozrast. Если было бы так, то сопоставление провалилось бы, и было бы брошено исключение.
В шаге 6 мы чуть-чуть подправили паттерн: заменили 3.14 на 3.15. И он уже не сработал, поэтому выскочила ошибка. Если бы мы оставили 3.14, то снова было бы хорошо. Правда уже немного по другой причине: все переменные в паттерне уже привязаны к значениям. Раз значения — те самые, значит, опять всё хорошо.
Кстати, вещественные числа в паттернах надо использовать очень осторожно. Вещественные числа это всегда примерно. Значение у выражения неожиданно может оказаться не 3.14, как мы ожидаем, а что-нибудь вроде 3.140000000000001. И тогда сопоставление провалится.
Как уже упоминалось, паттерны используются слева от знака равенства:
[X, Y] = [0.618, 1.618].
Также сопоставление с паттерном используется при определении функций. У каждой кляузы может быть свой паттерн, не похожий на другие. Когда функция вызывается и получает аргументы, они сравниваются с паттерном. Если сопоставление прошло успешно, то выполняется проверка гарды (если она имеется). Если и тут всё прошло успешно, выполняется данная кляуза. Если сопоставление провалилось, то проводится сопоставление по паттерну из следующей кляузы. Если нигде ничего не подошло, это вызовет исключение. В данном примере такого не случится, потому что шаблон _, _
подойдёт любым двум аргументам:
main(age, 17) -> "17 let";
main(age, 18) -> "18 let";
main(_, _) -> "I don't know.".
Паттерны используются в конструкции case ... end
. В следующем примере нам важно отделить случаи, когда переменная X это список из двух или трёх элементов, от иных случаев. Для этого мы используем паттерны: [_,_]
, [_,_,_]
и _
:
main(X) ->
case X of
[_,_] -> "List of two elements";
[_,_,_] -> "List of three elements";
_ -> "Other"
end.
Аналогично паттерны используются в блоках receive и try.
© Алексей Карманов, 2024.