Сопоставление

Сопоставление паттерна и терма — одна из ключевых, фундаментальных особенностей Эрланга. То же: 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.

Документация

Pattern Matching.


© Алексей Карманов, 2024.