Выпуск 13. Март 2014
← От редактора: Год журналу | Содержание | Сигнатура функции в Perl 5.20 →«Старые» современные возможности Perl
Расмотрены наиболее интересные, по мнению автора, особенности языка Perl
Perl это вообще весьма занятный язык программирования общего назначения. Многие говорят что он очень древний, в нем что-то не поддерживается, что это вообще убогий язык и нужно бы его закопать.
Новые языки программирования появляются постоянно. Вместе с этим добавляются новые аспекты программирования, новые архитектуры, новые модные приемы. А Perl как будто старый и не способен соревноваться.
Где-то давно я находил фразу приблизительно следующего содержания:
Многие люди когда видят перл чувствуют резкий разрыв шаблонов от того, что язык старый, не распиаренный корпорациями умеет то же самое, что языки, корпорациями распиаренные. А иногда даже и больше. Вот именно это основная тема статьи.
В рамках этой статьи я покажу, как можно делать замечательные и модные вещи на великолепном, хотя и старом языке.
Анонимные функции
Анонимные функции это особый вид функций, которые объявляются в месте использования и не получают уникального идентификатора для доступа к ним. Обычно при создании они либо вызываются напрямую, либо ссылка на функцию присваивается переменной, с помощью которой затем можно косвенно вызывать данную функцию.
Анонимная функция создается проще простого:
my $subroutine = sub {
my $msg = shift;
printf "I am called wit message: %s\n", $msg;
return 42;
};
# $subroutine теперь ссылается на анонимную функцию
$subroutine->("Oh, my message!");
Анонимные функции хорошо использовать как для создания блоков кода, так и для замыканий, которые, кстати, поддерживаются.
Замыкания
Как говорит нам википедия:
Замыкание — это особый вид функции. Она определена в теле другой функции и создаётся каждый раз во время её выполнения. В записи это выглядит как функция, находящаяся целиком в теле другой функции. При этом вложенная внутренняя функция содержит ссылки на локальные переменные внешней функции. Каждый раз при выполнении внешней функции происходит создание нового экземпляра внутренней функции, с новыми ссылками на переменные внешней функции.
# возвращает ссылку на анонимную функцию
sub adder($) {
my $x = shift; # в котором x - свободная переменная,
return sub ($) {
my $y = shift; # а y - связанная переменная
return $x + $y;
};
}
$add1 = adder(1); # делаем процедуру для прибавления 1
print $add1->(10); # печатает 11
$sub1 = adder(-1); # делаем процедуру для вычитания 1
print $sub1->(10); # печатает 9
Замыкания полезно использовать в той ситуации, когда необходимо получить функцию с уже готовыми параметрами, которые будут в ней сохранены. Или же, например, для генерации функции-парсера.
Неизменяемый объект
Нет ничего проще. Очень часто приходится видеть простыни текстов на форумах, как это реализовать. Многие «современные», «модные» языки не могут похвастаться настолько простым и изящным решением, как то, которое имеет Perl на такой случай. Фактически, state
работает как my
, у него одинаковая область видимости, но при этом переменная state НИКОГДА не будет переинициализирована в рамках программы. Это иллюстрируют два примера.
use feature 'state';
gimme_another();
gimme_another();
sub gimme_another {
state $x;
print ++$x, "\n";
}
Эта программа напечатает 1, затем 2.
use feature 'state';
gimme_another();
gimme_another();
sub gimme_another {
my $x;
print ++$x, "\n";
}
А эта 1, затем 1.
Бесскобочные функции
Я думаю, что это правильный перевод parentheses less.
Очень часто print
, например, пишется и вызывается без скобок. Возникает вопрос, а можем ли мы тоже создавать такие функции?
Можем. Для этого у Perl есть даже специальная прагма — subs
. Синтетический пример, в котором мы хотим сделать красивую проверку. Допустим, мы хотим напечатать OK, если определенная переменная true
. Я позволю себе напомнить, что в Perl нет такого типа данных, как Boolean, также, как и в C нет такого типа данных, тогда как в место него выступает не ложное значение — например, 1.
use strict;
use subs qw/checkflag/;
my $flag = 1;
print "OK" if checkflag;
sub checkflag {
return $flag;
}
Данная программа напечатает OK. Но это не единственный способ. Perl — довольно умен, а потому, если мы реструктуризируем нашу программу и приведем ее к такому виду:
use strict;
sub checkflag {
return $flag;
}
my $flag = 1;
print "OK" if checkflag;
То результат будет тот же. Закономерность здесь слудеющая. Мы можем вызывать функцию без скобок в нескольких случаях:
- Используя прагму subs.
- Написав функцию ПЕРЕД ее вызовом.
- Использовать прототипы функций.
Рассмотрим, что же такое прототипы.
Прототипы функций
Для начала стоит выяснить, как вообще функции работают в Perl.
Есть, например, какая-то абстрактная себе функция, которая называется, допустим my_sub
:
sub my_sub {
print join ', ', @_;
}
Например, мы ее вызываем следующим образом:
my_sub(1, 2, 3, 4, 5);
Функция напечатает следующее:
1, 2, 3, 4, 5,
Таким образом мы можем передавать в ЛЮБУЮ функцию Perl любое количество аргументов. И пусть сама функция разбирается, что мы от нее хотели.
В функцию передается «текущий массив», контекстная переменная. Поэтому, запись вида:
sub ms {
my $data = shift;
print $data;
}
Эквивалентна:
sub ms {
my $data = shift, @_;
print $data;
}
Логично предположить, что должен быть механизм по ограничению количества переданных аргументов в функцию. Этот механизм и называется прототипами.
Функция Perl с прототипами будет выглядеть так:
sub my_sub($$;$) {
my ($v1, $v2, $v3) = @_;
$v3 ||= 'empty';
printf("v1: %s, v2: %s, v3: %s\n", $v1, $v2, $v3);
}
Прототипы функций записываются после имени функции в круглых скобках. Прототип $$;$
означает, что в качестве параметров ОБЯЗАНЫ присутствовать два скаляра и третий по желанию, ;
— отделяет обязательные параметры от возможных.
Теперь, если мы ее попробуем вызвать вот так:
my_sub();
То мы получим ошибку вида:
Not enough arguments for main::my_sub at pragmaticperl.pl line 7, near "()"
Execution of pragmaticperl.pl aborted due to compilation errors.
Однако, если вызвать вот так:
&my_sub();
То проверка прототипов не будет происходить.
Важный момент. Прототипы будут работать в следующих случаях:
- Если функция вызывается без знака амперсанда (
&
). Perlcritic, кстати, ругается на запись вызова функции через амперсанд. Если функция написана ПЕРЕД вызовом. Если мы сначала вызовем функцию, а потом ее напишем, при включенных
warnings
получим следующее предупреждение:main::my_sub() called too early to check prototype at pragmaticperl.pl line 4.
Правильная программа с прототипами выглядит так:
use strict;
use warnings;
use subs qw/my_sub/;
sub my_sub($$;$) {
my ($v1, $v2, $v3) = @_;
$v3 ||= 'empty';
printf("v1: %s, v2: %s, v3: %s\n", $v1, $v2, $v3);
}
my_sub();
ООП
OOП у Perl странное. Кто-то говорит, что его нет, а кто-то говорит, что это лучшая реализация, т.к. она не мешает жить программисту, а позволяет делать сложные вещи возможными. Я для себя, например, еще не определился.
Что такое ООП в случае Perl? Это, так называемый, blessed hashref. Инкапсуляции, как таковой, в Perl нет. Есть сторонние модули, например, Moose
, Mouse
, которые предоставляю все возможности ООП Perl программисту ценой потери скорости.
Итак, простейший пакет.
use strict;
use Data::Dumper;
my $obj = Top->new();
print Dumper $obj;
$obj->top_sub();
package Top;
use strict;
sub new {
my $class = shift;
my $self = {};
bless $self, $class;
return $self;
}
sub top_sub {
print "I am TOP sub\n";
}
1;
Данная программа выведет:
$VAR1 = bless( {}, 'Top' );
I am TOP sub
Собственно, ООП сводится к тому, что есть объект и он может выступать хранилищем состояний и вызывать методы пакета. Через стрелочку ($obj->top_sub
).
Множественное наследование
Увы, как показыват практика и очень умные книги очень умных людей — если у вас в приложении появилась необходимость множественного наследования, то это приложение можно описать примерно следующим стишком.
ваш код сегодняшний коллега напоминает даунхилл среди деревьев и говнища велосипед и костыли
Но, Perl нам позволяет сделать и это. Далее приведен пример программы, которая наследует два класса:
use strict;
use Data::Dumper;
my $obj = Bottom->new();
print Dumper $obj;
# вызываем функции всех пакетов
$obj->top_sub();
$obj->top2_sub();
$obj->bottom_sub();
package Top;
use strict;
sub new {
my $class = shift;
my $self = {};
bless $self, $class;
return $self;
}
sub top_sub {
print "I am TOP sub\n";
}
package Top2 {
sub new {
my $class = shift;
my $self = {};
bless $self, $class;
return $self;
}
sub top2_sub {
print "I am TOP2 sub\n";
}
}
package Bottom;
use strict;
# наследуем Top и Top2
use base qw/Top Top2/;
sub new {
my $class = shift;
my $self = {};
bless $self, $class;
return $self;
}
sub bottom_sub {
print "I am BOTTOM sub\n";
}
1;
В данном случае программа выведет:
I am TOP sub
I am TOP2 sub
I am BOTTOM sub
Оверрайд методов
Оверрайд довольно часто хорошая штука. Например, у нас есть модуль, который писал некий N. И все в нем хорошо, а вот один метод, допустим, call_me, должен всегда возвращать 1, иначе беда, а метод из базовой поставки модуля возвращает всегда 0. Код модуля трогать нельзя.
Пусть программа выглядит следующим образом:
use strict;
use Data::Dumper;
my $obj = Top->new();
if ($obj->call_me()) {
print "Purrrrfect\n";
}
else {
print "OKAY :(\n";
}
package Top;
use strict;
sub new {
my $class = shift;
my $self = {};
bless $self, $class;
return $self;
}
sub call_me {
print "call_me from TOP called!\n";
return 0;
}
1;
Которая выведет:
call_me from TOP called!
OKAY :(
И снова у нас есть решение:
Допишем перед вызовом $obj->call_me() следующую вещь:
*Top::call_me = sub {
print "Overrided subroutine called!\n";
return 1;
};
И теперь наш вывод будет выглядеть примерно следующим образом:
Overrided subroutine called!
Purrrrfect
Код модуля не меняли, функция теперь делает то, что нам надо.
Программисту на заметку: Если приходится часто использовать данные прием в работе — налицо архитектурный косяк и вообще, я уже описывал эту ситуацию стишком. Хороший пример использование — добавление дебаг информации в функции.
Локальные переменные или динамическая область видимости:
Допустим, у нас есть скрипт, который что-то считает. Вдруг нам в функции, например, понадобилась локальная переменная, которая, пусть, должна быть копией глобальной.
Мы можем это сделать следующим образом:
use strict;
use warnings;
my $x = 1;
print "x: $x\n";
do_with_x();
print "x: $x\n";
sub do_with_x {
my $y = $x;
$y++;
print "y: $y\n";
}
Вывод ожидаемый:
x: 1
y: 2
x: 1
Однако, в данном случае мы можем обойтись без y
. Решение выглядит так:
use strict;
use warnings;
use vars '$x';
$x = 1;
print "x: $x\n";
do_with_x();
print "x: $x\n";
sub do_with_x {
local $x = $x;
$x++;
print "x: $x\n";
}
Эта штука и называется динамической областью видимости. Очень хорошо этот пример помогает понять представление переменной в виде карточек local
— это когда мы закрываем то, что написано на карточке другой карточкой, а как только выходим из блока, все возвращается на круги своя. Также очень часто эта конструкция используется внутри блоков кода в которых необходим автосброс буфера, тогда можно сделать:
local $| = 1;
Или, если лениво писать в конце каждого print \n
:
local $\ = "\n";
Атрибуты функций
В Python есть такое понятие, как декоратор. Это такая штуковина, которая позволяет «добавить объекту дополнительное поведение».
Да, в Perl декораторов нет, зато есть атрибуты функций. Если мы откроем perldoc perlsub
и посмотрим на описание функции, там есть такая любопытная запись:
sub NAME(PROTO) : ATTRS BLOCK
Таким образом, функция с атрибутами может выглядеть так:
sub mySub($$;$) : MyAttr {
print "Hello, I am sub with attributes and prototypes!";
}
Многие программисты, которые пишут на Perl ни разу не сталкивались ни с атрибутами, ни с прототипами. Причина этого кроется в литературе по языку для быстрого старта довольно низкого качества и сомнительного содержания. Но мы работаем над этим.
И скоро даже что-нибудь покажем. Все вышесказанное не касается книги с Верблюдом, Альпакой и Ламой, это прекрасная литература и тем кто ее не читал — я настоятельно рекомендую. Вернемся к нашим атрибутам.
Работа с атрибутами в Perl дело нетривиальное, потому уже довольно давно в стандартную поставку Perl входит модуль Attribute::Handlers
.
Дело в том, что атрибуты из коробки имеют довольно много ограничений и нюансов работы, так что если кому интересно — мой почтовый адрес есть в статье и мне можно всегда написать.
Допустим, у нас есть функция, которая может быть вызвана только в том случае, если пользователь авторизован. За то, что пользователь авторизован отвечает переменная $auth
, которая равна 1, если пользователь авторизован и 0, соответственно, нет. Мы можем сделать следующим образом:
my $auth = 1;
sub my_sub {
if ($auth) {
print "Okay!\n";
return 1;
}
print "YOU SHALL NOT PASS!!!1111";
return 0;
}
И это нормальное решение.
Но может возникнуть такая ситуация, что функций будет становиться больше и больше. А в каждой делать проверку будет становиться накладно. Проблему можно решить на атрибутах.
use strict;
use warnings;
use Attribute::Handlers;
use Data::Dumper;
my_sub();
sub new {
return bless {}, shift;
}
sub isAuth : ATTR(CODE) {
my ($package, $symbol, $referent, $attr, $data, $phase, $filename, $linenum) = @_;
no warnings 'redefine';
unless (is_auth()) {
*{$symbol} = sub {
require Carp;
Carp::croak "YOU SHALL NOT PASS\n";
goto &$referent;
};
}
}
sub my_sub : isAuth {
print "I am called only for auth users!\n";
}
sub is_auth {
return 0;
}
В данном примере вызов программы будет выглядеть так:
YOU SHALL NOT PASS at myattr.pl line 18. main::__ANON__() called at myattr.pl line 6
А если мы заменим return 0 на return 1 в is_auth
, то:
I am called only for auth users!
Я не зря написал про атрибуты в конце статьи. Для того, чтобы написать этот пример мы воспользовались:
- Анонимными функциями.
- Оверрайдом функций.
- Специальной формой оператора goto.
Не смотря на довольно громоздкий синтаксис атрибуты успешно применяются в Catalyst
, например.
К тому же, не стоит забывать, что они, все-таки, являются экспериментальной фичей Perl, а потому их синтаксис может меняться.
О чем хотелось бы еще написать и я напишу:
- Фазы исполнения Perl программы.
- Более пристальный взгляд на модули.
- AUTOLOAD и DESTROY.
- Особая форма оператора goto.
- Прагмы Perl.
До новых встреч.
← От редактора: Год журналу | Содержание | Сигнатура функции в Perl 5.20 →