среда, 26 октября 2011 г.

Интересности Perl

Как-то совсем давно я ничего не писал, думаю уже пора :). Не так давно прошла конференция BlackPerl 2011. Я думаю, что уже многие в курсе относительного этого события.
Я на конференцию ехал без доклада, просто с желанием познакомится с Perl-комьюнити, послушать других и пофотографировать сие действо. Но вдохновленный докладчиками, решил и сам соорудить небольшое выступление. Поскольку, конкретной темы у меня не было, я решил просто рассказать про интересные вещи в Perl. Ничего особенного, но возможно кто-то найдет для себя что-то новое.

Приступим.

Приватные методы
В Perl возможно реализовать приватные методы и делается это следующим образом:
# Private accessors/methods
# Помещаем ссылку на локальную анонимную функцию в локальную переменную
my $name = sub {
my $self = shift;
...
}
# Используем как обыкновенный метод, но доступен оно только внутри класса
$self->$name();
$self->$name('viktor');

Недостаток такого подхода в том, что caller будет нам возвращать "__ANON__" в качестве имени нашего метода. И stack trace будет нечитабельным.


Это проблему призван решить модуль  "Sub::Name" , он позволяет давать имена анонимным функциям. Эти имена будут показываться в стеке вызовов, но их нельзя использовать для вызова функции/метода.

Наш пример приобретет вид:
# Private accessors/methods with correct "caller"
use Sub::Name;
# Помещаем ссылку на локальную анонимную функцию в локальную переменную
# и присваиваем ей имя "name"
my $name = subname "name" => sub {
my $self = shift;
...
}
# Используем как обыкновенный метод, но доступен оно только внутри класса
$self->$name();
$self->$name('viktor');


Истина или Ложь

Вы встречали такой код - "$sth->execute() or die;" , а задумывались как он может работать, если "execute" возвращает количество измененных рядков для не "select" запросов. Ведь если по условию WHERE при UPDATE мы не нашли ничего, то это ж не является ошибкой. Получается, что метод возвращает нулевое истинное значение.

my $rows_affected = $dbh->do('UPDATE ... WHERE') or die;
my $rows_affected = $sth->execute(...) or die; # Для не "select" запросов.
# $rows_affected может содержать ноль, но быть истинным.
# Значит в Perl может быть истинное нулевое значение
# И вот несколько вариантов при использовании
# которых Perl не сыпет предупреждения
"0.0" # Десятичная нотация. Я обычно использую именно этот вариант
"0E0" # Экспоненциальный ноль (такой вариант использует DBI)
"+0" # Положительный ноль
" 0" # Пробел перед нулем
view raw 0_but_true.pl hosted with ❤ by GitHub


И вот самый интересный вариант нулевого истинного значения:
# строка, которая в числовом контексте соответствуют
# нулю, а в логическом - истине
"0 but true"
perl -wE 'say "0 but true"+5'
#Получим 5 без никаких предупреждений
# Но стоит изменить хоть один символ в заветной строке
perl -wE 'say "0 but true!"+5'
'Argument "0 but true!" isn't numeric in addition (+) at -e line 1.
# И мы сразу получим предупреждение


Локализируемся

Просто несколько нюансов при работе с функцией local
#---------------------------------------------------------#
# Локализация пакетных переменных
our $l = 5;
{
local $l = 4;
say $l
};
say $l;
#4
#5
#---------------------------------------------------------#
# Локализация переменных с лексической областью видимости
my $l = 5;
{
local $l = 4;
say $l
};
say $l;
# В результате получим ошибку
#Can't localize lexical variable $l at -e line 3.
#---------------------------------------------------------#
# Но возможна локализация элементов локально
# объявленных массивов/хешей
my @l = (2,3,5);
{
local $l[2] = 4;
say $l[2];
};
say $l[2];
#4
#5
#---------------------------------------------------------#
# Может быть полезно, например, в такой ситуации
{
local $h->{RaiseError}; # no RaiseError
...
}
view raw perl_local.pl hosted with ❤ by GitHub


Однострочник для чтения файла
$content = do {local(@ARGV, $/) = $f; <ARGV>}

В Perl есть файловый хендлер ARGV, с которого мы можем вычитать файлы переданные скрипту в качестве аргументов командной строки. В примере происходит следующее:
1. Мы помещаем имя файла в массив @ARGV, который должен содержать переданные скрипту аргументы
2. Локализируем "$/" значением undef
3. Вычитываем файл в переменную

При этом, можно вычитать несколько файлов одним потоком:
perl -e 'while (<ARGV>){ … }' f1 f2 f3


@INC
Многие знают, что массив @INC содержит список путей, которые Perl использует для поиска подключаемых модулей. Но кроме путей @INC может содержать хуки в виде ссылки на функцию, объект, массив. В прагме "use everywhere" используется данная возможность.
use everywhere 'MooseX::Declare',
matching => '^MyApp',
use_here => 0;

Детали можно прочитать в "perldoc -f require"


Осторожно! Пустой регексп
Я думаю многих удивит результат выполнения такого вот кода:

if ( "test1" =~ /t1/ ) {
say "YES";
} else {
say "NO"
}
if ( "test2" =~ // ) {
say "YES";
} else {
say "NO"
}
#YES
#NO
# Второй регексп не заматчится!!!


Причина в том, что Perl на место пустого регекспа поставит последний удачно совпавший регексп.
$text =~ m/$re/;
# Такая запись тоже является опасной, поскольку
# переменная $re может содержать пустую строку
# Лучше записывайте так
$text =~ m/(?:$re)/;


Переменная по-умолчанию

# Опасный код!!!
my @ar = qw/h e l l o/;
foreach (@ar) {
do_something();
say $_;
}
# Никогда не используйте переменную "$_"
# если в цикле вызывается какая-то внешняя функция.
# Если вдруг где-то выполнится код похожий на:
...
open (my $fh, '<', 'file');
while(<$fh>) {
...
}
...
# то
# 1. "say $_;" - ничего хорошего не выдаст
# 2. Все элементы массива "@ar" будут испорчены
# поскольку "$_" является алиасом, а не копией


Переменные окружения
Многие используют переменную окружения PERL5LIB для указания пути к перловым модулям. Но кроме этой переменной окружения есть еще PERL5OPT, позволяющая задать опции командной строки, которые передаются интерпретатору Perl при запуске. Например, можно подключить определенные модули:
export PERL5OPT='-MData::Dumper'
И затем всегда Data::Dumper будет доступен в ваших однострочниках:
perl -e 'print Dumper([1,2])'

Try::Tiny
Этот момент я освещал уже в своем блоге.
В посте "Чем плох eval?!" были описаны проблемы перехвата исключений в Perl и я рекомендовал использовать модуль "Try::Tiny". Но с "Perl 5.14" необходимость в "Try::Tiny" практически отпала, это было описано в посте "Наконец-то исправлена обработка исключений в Perl 5.14"

Материалы по конференции

5 коммент.:

Анонимный комментирует...

Давать именам анонимным сабрутинам очень просто:
local *__NAME__ = 'subname'

Анонимный комментирует...

s/NAME/ANON/

koorchik комментирует...

Я так понимаю, что это мы просто зададим имя для всех анонимных функций. Хотелось бы увидеть как Вы реализуете аналогичное такому:

use v5.10;
use Sub::Name;
my $name1 = subname name1 => sub {
say( (caller(0))[3] );
say( (caller(1))[3] );
say( (caller(2))[3] );
};
my $name2 = subname name2 => sub { $name1->() };
my $name3 = subname name3 => sub { $name2->() };
$name3->();

# Результат будет таким
#main::name1
#main::name2
#main::name3

Анонимный комментирует...

По стеку вызовов с такой конструкцией ходить не получится, да.
Имя задается не для всех анонимных функций, а для текущей выполняемой (local же).

koorchik комментирует...

> Имя задается не для всех анонимных функций, а для текущей выполняемой (local же).
local позволяет нам лишь временно задать имя в пределах некого блока, но имя будет актуально для всех анонимных функций( если они не переопределят его уже сами ). По этой причине caller и не работает, как мы хотим.

Отправить комментарий

Не забудьте добавить себя в постоянные читатели и включить уведомления о новых комментариях, либо воспользуйтесь RSS каналом ;)