Оператор умного сравнения

Оператор умного сравнения ~~ проверяет оба операнда и самостоятельно решает, как он их будет сравнивать. Если операнды выглядят как числа, выполняется числовое сравнение. Если операнды выглядят как строки, оператор сравнивает их как строки. Если один операнд содержит регулярное выражение, выполняется поиск по шаблону. Оператор даже способен выполнять сложные задачи, решение которых потребует большого объема кода, избавляя вас от ввода лишних символов. Оператор ~~ внешне напоминает оператор привязки =~, но оператор ~~ способен на большее. Более того, он даже может заменить оператор привязки. Ранее мы использовали оператор привязки для того, чтобы связать $name с оператором поиска совпадения по регулярному выражению:

print "I found Fred in the name!\n" if $name =~ /Fred/;

Если заменить оператор привязки оператором умного сравнения, программа будет делать абсолютно то же самое:

use 5.010;
say "I found Fred in the name!" if $name ~~ /Fred/;

Оператор умного сравнения видит, что в левой части находится скалярное значение, а в правой – оператор поиска совпадения, и самостоятельно определяет, что от него требуется. Впрочем, это только начало.


Далее все будет намного интереснее. Возможности оператора умного сравнения в полной мере проявляются при более сложных операциях. Допустим, вы хотите вывести сообщение, если в одном из ключей хеша-names совпадает шаблон Fred. Использовать exists не удастся, эта функция проверяет только точное значение ключа. Можно создать цикл foreach, который проверяет каждый ключ по оператору регулярного выражения и пропускает ключи, в которых совпадение не обнаружено. Обнаружив ключ с совпадением, мы изменяем значение переменной $flag и пропускаем остальные итерации командой last:

my $flag = 0;
foreach my $key ( keys %names ) {
next unless $key =~ /Fred/;
$flag = $key;
last;
}
print "I found a key matching 'Fred'. It was $flag\n" if $flag;

Ого! Даже объяснение получается слишком длинным, хотя такое решение работает в любой версии Perl 5. Но с оператором умного сравнения вы просто ставите в левой части хеш, а в правой части – оператор регулярного выражения:

use 5.010;
say "I found a key matching 'Fred'" if %names ~~ /Fred/;

Оператор умного совпадения знает, что делать, потому что он видит хеш и регулярное выражение.


Он знает, что с такими операндами нужно взять ключи -names и применить регулярное выражение к каждому из них. Если оператор найдет совпадение, он останавливается и возвращает true. При получении скалярного значения и регулярного выражения оператор действовал бы иначе. Именно поэтому оператор ~~ называется «умным»; он делает то, что лучше подходит для текущей ситуации. Оператор остается прежним, но выполняемые им действия изменяются. Допустим, вам понадобилось сравнить два массива (с одинаковым количеством элементов). Можно перебрать индексы одного массива и в каждой итерации сравнить соответствующие элементы двух массивов. Каждый раз, когда элементы совпадают, в программе увеличивается счетчик $equal. Если после завершения цикла значение $equal совпадает с количеством элементов @names1, массивы совпадают:

my $equal = 0;
foreach my $index ( 0 .. $#names1 ) {
last unless $names1[$index] eq $names2[$index];
$equal++;

}
print "The arrays have the same elements!\n"
if $equal == @names1;

И снова получается слишком много работы. Нельзя ли то же самое сделать как-то проще? Постойте-ка! А как насчет оператора умного сравнения? Передайте два массива оператору ~~.


Следующий небольшой фрагмент делает то же, что и предыдущий пример, но с минимумом программного кода:

use 5.010;
say "The arrays have the same elements!"
if @names1 ~~ @names2;

Еще один пример. Допустим, мы вызываем функцию и хотим убедиться в том, что ее возвращаемое значение входит в множество возможных или ожидаемых значений. Она всегда должна возвращать одно из переданных ей значений. Что-бы убедиться в этом, можно проверить возвращаемое значение max по списку аргументов «вручную», как это делалось в предыдущих трудных решениях:

my @nums = qw( 1 2 3 27 42 );
my $result = max( @nums );
my $flag = 0;
foreach my $num ( @nums ) {
next unless $result == $num;
$flag = 1;
last;
}
print "The result is one of the input values\n" if $flag;

Вы уже знаете, что мы скажем: слишком много работы! Оператор ~~ позволяет исключить из этого кода весь средний фрагмент. Решение заметно упрощается:

use 5.010;
my @nums = qw( 1 2 3 27 42 );
my $result = max( @nums );
say "The result [$result] is one of the input values (@nums)"
if @nums ~~ $result;

Даже если записать операнды в другом порядке, результат от этого не изменится.


Оператор умного сравнения не обращает внимания, с какой стороны записан тот или иной операнд:

use 5.010;
my @nums = qw( 1 2 3 27 42 );
my $result = max( @nums );
say "The result [$result] is one of the input values (@nums)"
if $result ~~ @nums;

Оператор умного сравнения обладает свойством коммутативности. Возможно, вы помните из школьного курса алгебры, что этот ученый термин означает лишь то, что порядок операндов неважен. Оператор умного сравнения в этом отношении напоминает операции сложения или умножения; перестановка операндов не влияет ни на способ сравнения, ни на ответ. Следующие две команды эквивалентны:

use 5.010;
say "I found a name matching 'Fred'" if $name ~~ /Fred/;
say "I found a name matching 'Fred'" if /Fred/ ~~ $name;

Оцените статью: (0 голосов)
0 5 0

Статьи из раздела Perl на эту тему:
Команда given
Обычное сравнение
Приоритеты умного сравнения
Условия when с несколькими элементами

Вернуться в раздел: Perl / 14. Умные сравнения и given)when