Выпуск 6. Август 2013
← DBIx::Class в примерах | Содержание | Обзор CPAN за июль 2013 г. →Секретные операторы Perl и не только
Рассмотрены интересные неочевидные конструкции Perl.
Как известно, язык программирования Perl очень выразителен и имеет в своем арсенале множество средств, которые позволяют выразить намерения программиста в коде множеством совершенно разных образов. Также, в виду весьма хитрого синтаксиса, некоторые комбинации операторов могут приводить к интересным эффектам.
Также, некоторые вещи можно использовать не совсем стандартным образом для получения желаемого результата. Такие действия иногда называются «забивать гвозди микроскопом». Именно этим мы и будем заниматься.
Прежде чем читать эту статью дальше, необходимо понимать несколько вещей:
- Поведение операторов, приведенных в этой статье, может меняться от версии к версии Perl.
- Данные операторы, скорее всего, не предназначены для использования их в production.
- Большинство этих операторов были созданы людьми, которым приносит удовольствие исследовать любимый язык.
- Все, что приведено ниже и названо операторами, на самом деле ими не является.
Поехали.
Оператор «Венера»
0+
+0
Название
Свое название оператор получил от внешней схожести с символом Венеры.
Что делает?
Приводит аргумент слева, или справа, в зависимости от версии, к числовому виду. Например:
print 0+ '23a';
print 0+ '3.00';
print 0+ '1.2e3';
print 0+ '42 EUR';
print 0+ 'two cents';
Результат:
23
3
1200
42
0
Этот оператор с натяжкой можно использовать у себя в проектах, но не следует забывать о том, что с точки зрения Perl числами, например, являются: 0 but true 0E0 и некоторые еще хитрые константы. Еще следует отметить, что 0+
это метод, используемый для числового преобразования по умолчанию при использовании overload
.
«Черепашка», «Детская коляска» или «Тележка из супермаркета»
@{[ ]}
Название
Свое название оператор получил из-за внешней схожести с черепахой или коляской. Примечателен тем, что был открыт Ларри Уоллом в 1994 году.
Что делает?
Это так называемый контейнерный оператор, который позволяет производить интерполяцию массива внутри строки. Элементы массива в результате будут разделены содержимым переменной $"
.
Как работает?
Сначала содержимое [ ]
принудительно вычисляется в списковом контексте, затем, незамедлительно, проводится разыменование (@{ }
).
Пример:
print "Test i am @{[ die()]}";
print "here";
Результат работы:
Died at demo.pl line 1.
Этот оператор можно использовать для выполнения произвольного кода в строках, когда происходит интерполяция. Например, с его помощью можно сделать некоторые вещи проще, например, построение SQL-запросов:
my $sql = "SELECT id, name, salary
FROM employee WHERE id IN (@{[ keys %employee ]})
SQL
";
но выигрыш в пару строк кода не оправдывает потенциальные проблемы в безопасности (хорошая практика в SQL — использование т.н. bind variables, которые будут подставлены при prepare). В виду неочевидности, использовать оператор в production не стоит, потенциальная уязвимость и уменьшение читабельности.
Bang Bang
!!
Данный оператор использовался еще тогда, когда Perl не было, его часто использовали программисты на С.
Как работает?
Этот оператор делает следующую элементарную вещь — двойное отрицание, суть которого сводится к булевому преобразованию. Вспоминаем, что булевых типов в Perl нет.
my $true = !! 'a string';
my $false = !! undef;
В результате $true
содержит 1
, а $false
пустую строку ''
. Данный оператор можно использовать для проверки, есть ли значение, например, в ссылке на хеш, например
!! $hash->{value} or die("Missing value");
Червяк
~~
Название?
Просто и незатейливо похож на гусеницу-пяденицу, и на многих червей также.
Как работает?
Оператор ~~
внешне очень похож на smart matching, но им не является потому, что это унарный оператор.
Унарная операция — это операция над одним операндом
Тогда как smart matching — операция бинарная (операция над двумя операндами, сложение, например).
Что делает?
Всего-лишь сокращенное на четыре (!) символа ключевое слово scalar
.
Пример:
perl -Esay~~localtime
Tue Jul 30 17:43:16 2013
Принцип действия оператора схож с оператором Bang Bang (!!
), но отличается тем, что в Perl оператор ~
операторозависимый. Более подробно можно узнать, посмотрев в документации про побитовые операции в Perl. Применять этот оператор можно, но следует быть осторожным, неизвестно, как он будет себя вести на всех версиях perl.
Червяк-на-палочке
-~
~-
Это высокоприоритетный оператор инкремента/декремента. -~
инкрементирует только числа, которые меньше нуля, а ~-
декрементирует числа больше нуля
Приоритет этого оператора выше арифметических операторов, кроме возведения в степень (**
). Например:
$y = ~-$x * 4;
Будет исполняться идентично:
$y = ($x-1)*4;
Но не как:
$y = ($x * 4) -1
Для того, чтобы данные операторы работали с беззнаковыми типами данных, необходимо использовать прагму integer
(use integer
). Этот оператор работает весьма неплохо и его можно применять в production, но делать этого не стоит, т.к. на нестандартных архитектурах его поведение может отличаться от вышеуказанного. Хотелось бы попробовать его в действии на ARM-платформах, но автор статьи не располагает подобным устройством.
Космическая станция
-+-
Открыт Alistair McGlinchy в 2005 году.
Этот оператор производит высокоприоритетное приведение к числовому виду, по поведению он похож на оператор «символ Венеры», но он отличается следующими вещами. Оператор Венеры (0+
или +0
) использует бинарный оператор +
, тогда как «Космическая станция» использует сконкатенированные три унарных оператора, а потому имеет более высокий приоритет.
Также, стоит помнить, что этот оператор имеет меньший приоритет, чем сконкатенированные операторы *
и x
. Принципы его работы можно проиллюстрировать следующим примером, в котором мы попробуем распечатать приведенное к числовому виду '20GBP'
три раза:
Неправильно, т.к. возвращает преобразованный вариант от строки '20GBP20GBP20GBP'
:
print 0+ '20GBP' x 3; # 20
Неправильно, т.к. эквивалентно (print '20' ) x 3
:
print( 0+ '20GBP' ) x 3; # 20
Правильно, но сильно длинно, сильно «лиспово»:
print( ( 0 + '20GBP' ) x 3 ); # 202020
Правильно — используя оператор «Космическая станция»:
print -+- '20GBP' x 3; # 202020
Однако, т.к. унарный минус -
и унарный +
просто заменяет результат строки на его значение, то данный оператор не будет работать со следующего вида строками:
- С теми, что начинаются на
-
. - С теми, что начинаются на не числовой символ.
Тогда как оператор Венеры работает во всех этих случаях, но имеет меньший приоритет.
Goatse или Сатурн-оператор
=()=
Дабы не травмировать ничью психику, автор статьи не рекоммендует искать в интернете первое название оператора, если оно вам кажется незнакомым. Правда, не надо, вам не понравится.
Что делает?
Этот оператор вводит списковый контекст справа от него и возвращает количество элементов слева.
Как работает?
Список в скалярном контексте возвращает количество элементов. Не имеет значения, сколько элементов из них было присвоено переменным. В таком случае правая часть выражения будет приведена к пустому списку, а затем, следовательно, отброшена.
Пример:
Посчитать количество слов в $_
:
$n =()= /word1|word2|word3/g;
$n =()= "abababab" =~ /a/; # $n = 1
$n =()= "abababab" =~ /a/g; # $n = 4
К тому же, данный оператор является контейнерным (!), что позволяет запросто «втянуть» в него результат правой части. Это значит, что мы можем поступать следующим образом:
$n =($b)= "abababab" =~ /a/g; # $n = 4; $b = 'a'
$n =(@c)= "abababab" =~ /a/g; # $n = 4; @c = qw( a a a a )
Следующий «хитрый» пример его использования, похоже, имеет право на жизнь, но существует другой секретный оператор, который может делать то же самое.
Допустим, мы хотим узнать, на сколько частей разобьет строку split, а сами элементы нас не интересуют, может быть, есть смысл попробовать нечто следующего вида:
my $count = split /:/, $string;
Этот пример вернет нам необходимое число, но при этом ругнется на:
Use of implicit split to @_ is deprecated
Для того, чтобы решить данную проблему мы можем воспользоваться данным оператором и написать нечто подобное:
my $count =()= split /:/, $string;
Что не вызовет warning, но при этом будет всегда возвращать 1 потому, что split
никогда не разбивает строку на большее количество частей, чем необходимо. В свою очередь, компилятор расценивает попытку сохранить в ()
как утверждение в том, что данные элементы нам не надо и вернет неизмененную строку, что будет приведено к списку с одним элементом.
Есть два возможных решения.
Первое заключается в том, что мы можем запретить компилятору проводить оптимизацию split()
, указав при помощи -1 желание получить бесконечное количество кусков строки:
my $count =()= split /:/, $string, -1;
Или использовать другой секретный оператор, «черепашку»:
my $count = @{[ split /:/, $string ]};
Воздушный змей
~~<>
На самом деле, данный оператор является всего-лишь комбинацией Червяка и <>
. Он предоставляет скалярный контекст для операции readline()
, но полезный он только в контексте списка.
Богато украшенный двусторонний меч
<<m=~m>> m ;
Этот секретный оператор предоставляет мультистрочные комментарии и не более того. Пример использования:
<<m=~m>>
Use the secret operator on the previous line.
Put your comments here.
Lots and lots of comments.
You can even use blank lines.
Finish with a single
m
;
Но следует учитывать тот факт, что данный комментарий — просто строка, заключенная в двойные кавычки, а потому может иметь некоторые побочные эффекты.
Отверточные операторы
Обнаруженный Дмитрием Карасиком в процессе поиска операторов, базирующихся на !
. Как и отвертки, эти операторы бывают четырех основных типов, но с разной длиной рукоятки:
Прямая отвертка — обеспечивает декремент по условию:
$x -=!! $y
# $x-- if $y;
$x -=! $y
# $x-- unless $y;
Крестовая отвертка — инкремент по условию:
$x +=!! $y;
# $x++ if $y;
$x +=! $y;
# $x++ unless $y;
Отвертка-звездочка — сброс переменной в 0 по условию:
$x *=!! $y;
# $x = 0 unless $y;
$x *=! $y;
# $x = 0 if $y;
Крестообразная отвертка-шлиц — сброс переменной в ’’ по условию:
$x x=!! $y;
# $x = '' unless $y;
$x x=! $y;
# $x = '' if $y;
Enterprise-оператор
()x!!
Довольно часто возникает необходимость добавить элемент в список по условию. Это можно сделать следующим образом:
my @shopping_list = ('bread', 'milk');
push @shopping_list, 'apples' if $cupboard{apples} < 2;
push @shopping_list, 'bananas' if $cupboard{bananas} < 2;
push @shopping_list, 'cherries' if $cupboard{cherries} < 20;
push @shopping_list, 'tonic' if $cupboard{gin};
Но при помощи этого оператора можно сделать следующим образом:
my @shopping_list = (
'bread',
'milk',
('apples' )x!! ( $cupboard{apples} < 2 ),
('bananas' )x!! ( $cupboard{bananas} < 2 ),
('cherries' )x!! ( $cupboard{cherries} < 20 ),
('tonic' )x!! $cupboard{gin},
);
Скобки необходимы из-за возможных проблем с приоритетом выполнения.
Секретные константы
Космический флот
<=><=><=>
На первый взгляд эта конструкция похожа на космический корабль, но на самом деле, действительно, космическим кораблем является только средний <=>
. Крайние корабли — вызов glob("=")
. Константа = 0.
Двуходка
<~>
На операционных системах семейства Unix — реальный путь к домашнему каталогу полозователя, на Windows — значение переменной окружения $ENV{HOME}
.
Еще раз напоминаю, что большинство данных операторов были придуманы или исключительно шутки ради, или ради изучения механизмов языка.
← DBIx::Class в примерах | Содержание | Обзор CPAN за июль 2013 г. →