Pull to refresh

Особенности национальных выражений

Reading time 2 min
Views 1K
Захотелось мне поделиться с хабравчанами-перловодами одним интересным случаем, произошедшим у нас на работе. В процессе ревизии старого кода была обнаружена некая загадочная конструкция (здесь и далее реальный код несколько сокращён и приглажен):
sort { my ($x, $y) = ($a, $b); ($x =~ s{/}g) <=> ($y =~ s{/}g); } @array;
Казалось бы, обычная сортировка с переопределённой функцией сравнения. По изначальной задумке автора здесь должна была быть сортировка массива строк по количеству прямых слэшей в этих строках (известно, что выражение замены возвращает в качестве значения количество произведённых замен), однако что-то в этой функции не то. Вроде бы, «заменительная» часть выражения s/pattern/replace/g никогда не была опциональной, однако именно это мы видим в выражениях $a =~ s{/}g. Как же оно работает? Вот с этим мы сейчас и будем разбираться.

Итак, первое, что приходит в голову, — просто попробовать выражение на простеньком примере. Хорошо, запускаем и получаем результат:
$ perl -we 'my $a = "a/b/c"; $a =~ s{/}g; print "$a\n";'
Substitution replacement not terminated at -e line 1.
То есть наши подозрения были верны, выражение невалидно. Как же тогда работает исходный пример? После безуспешных попыток разобраться своими силами мы призвали на помощь модуль Deparse.
$ cat test.pl
my ($a, $b);
sub f {
    return (($a =~ s{/}g) <=> ($b =~ s{/}g));
}
$ perl -MO=Deparse -w test.pl
BEGIN { $^W = 1; }
my($a, $b);
sub f {
return $a =~ s[/][) <=> ($b =~ s{/}];
}
/home/tester/test.pl syntax OK
После недолгого скрипения мозгами, наконец, удалось разобраться, что же делает Перл. В вышеприведённом логе я подсветил синим скобки, на которые нужно обратить внимание. Поведение оказалось вполне логичным и документированным, хотя и несколько неожиданным. Как должно быть известно многим из программистов на Перле, в операторе s/// в качестве скобок вместо слэшей могут использоваться и другие символы (что очень наглядно продемонстрировал Deparse, использовав квадратные скобки). Но до этого момента мне просто не приходило в голову, что точно так же в качестве скобок может быть использована самая обычная буква "g"! Таким образом, в качестве выражения для поиска в переменной $a берётся то, что и должно — один прямой слэш, а вот в качестве выражения для замены берётся текст, находящийся между буквами "g", которые предполагались быть модификаторами операции.

Для наглядности я сейчас снова напишу исходную строку кода, на этот раз подсветив красным цветом выражение поиска, зелёным — замены, а «скобки» обозначив тем же цветом, но более тёмного тона:
(($a =~ s{/}g) <=> ($b =~ s{/}g))
В результате мы имеем одно выражение замены, а не два, и наша функция сравнения на самом деле ничего не сравнивала, а просто всегда выдавала лишь количество слэшей в первом аргументе, и вместо сортировки мы получали просто перемешивание массива (а не заметили этого раньше лишь по той причине, что сортировка была чисто косметическим улучшением, и при использовании программы на порядок вывода сортируемых строк никто не обращал особого внимания).

Итак, какая же будет мораль? Да, наверное, никакая. Лишний раз подтвердилась и без того всем известная истина: не всё то верно, что компиляется.
Tags:
Hubs:
+32
Comments 54
Comments Comments 54

Articles