Определение пользовательской функции

Пользовательская функция, чаще называемая подпрограммой, определяется в Perl-программе с помощью следующей конструкции:

имя {

оператор_1

;

оператор_2

;

оператор

3;

Здесь имя

- это имя подпрограммы, которое может быть любым именем вроде тех, которые мы давали скалярным переменным, массивам и хешам. Вновь подчеркнем, что эти имена хранятся в другом пространстве имен, поэтому у вас может быть скалярная переменная $fred, массив @fred, хеш %fred, а теперь и подпрограмма fred*. Более правильно эту подпрограмму следовало бы назвать sfred, но пользоваться такого рода именами приходится редко.

Блок операторов, следующий за именем подпрограммы, становится ее определением. Когда эта подпрограмма вызывается, то блок операторов, входящих в нее, выполняется, и вызывающему объекту выдается соответствующее возвращаемое значение (как будет описано ниже).

Вот, например, подпрограмма, которая выдает знаменитую фразу:

print "hello, world!\n";

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

- в конец. Это ваше дело.)

Определения подпрограмм глобальны*; локальных подпрограмм не бывает. Если у вас вдруг появились два определения подпрограмм с одинаковым именем, то второе определение заменяет первое без предупреждения**.

В теле подпрограммы вы можете обращаться к переменным, используемым в других частях программы (глобальным переменным), и присваивать им значения. По умолчанию любая ссылка на переменную в теле подпрограммы относится к глобальной переменной. Об исключениях из этого правила мы расскажем в разделе "Локальные переменные в функциях" .

В следующем примере:

print "hello, $what\n";

переменная

$what является глобальной переменной, которая может использоваться также и в других частях программы.

Вызов пользовательской функции

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

простое выражение = 3 + say_hello() # часть более сложного выражения ($х = start_value(); $х < end_value(); $х += increment О) ( вызов трех подпрограмм для определения значений Точнее, глобальны для текущего пакета, но поскольку в этой книге отдельные пакеты не рассматриваются, вы можете считать определения подпрограмм глобальными для всей программы. Если только вы не выполняете программу с ключом -w.

Одна подпрограмма может вызывать другую подпрограмму, которая, в свою очередь, может вызывать третью подпрограмму и т.д., пока вся наличная память не будет заполнена адресами возврата и не полностью вычисленными выражениями. (Ведь настоящего программиста вряд ли удовлетворят какие-то

8 или 32 уровня вложенности подпрограмм.)

Возвращаемые значения

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

return или последнего выражения, вычисленного в подпрограмме.

Давайте, например, определим такую подпрограмму:

sub sum_of_a and_b (return $a + $b;

Последнее выражение, вычисляемое в теле этой подпрограммы (фактически единственное вычисляемое выражение),

- сумма переменных $а и $ь, поэтому эта сумма и будет возвращаемым значением. Вот как все это работает: = 3; $b = 4; = sum_of_a_and_b(); # $с присваивается значение 7 $d = 3 * sum_of_a_and_b(); # $d содержит значение 21

При вычислении в списочном контексте подпрограмма может возвращать список значений. Рассмотрим такую подпрограмму и ее вызов:

sub Ii st_o f_a_and_b {

@c = list_of_a_and_b(); # @c

присваивается значение (5,6)

Последнее вычисленное выражение действительно означает последнее вычисленное выражение, а не последнее выражение, определенное в теле подпрограммы. Например, следующая подпрограмма возвращает $а, если $а

> 0; в противном случае она возвращает $Ь:

sub gimme a_or_b (if ($a > 0) (

print "choosing a ($a)\n";

print "choosing b ($b)\n";

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

Аргументы

Несомненно, подпрограммы, выполняющие одно определенное действие, полезны, однако перед вами откроются совершенно новые горизонты, когда вы сможете передавать в подпрограмму аргументы.

Perl после вызова подпрограммы следует список, заключенный в круглые скобки, которые обеспечивают автоматическое присваивание элементов данного списка на период выполнения этой подпрограммы специальной переменной с именем @_. Подпрограмма может обратиться к этой переменной и получить число аргументов и их значения. Например:

sub say_hello_to (

print "hello, $_!\n" #

первый параметр } - первый элемент массива @_. Обратите внимание: несмотря на внешнее сходство, значение $_ [ 0 ] (первый элемент массива @_) не имеет ничего общего с переменной $_ (самостоятельной скалярной переменной). Не путайте их! Из этого кода видно, что подпрограмма приветствует того, чье имя мы указываем в качестве первого параметра. Это значит, что ее можно вызвать так:

say_hello_to("world"); #

выдает hello, world $х = "somebody";

say_hello_to($x); #

выдает hello, somebody say_hello_to("me")+say_hello_to("you"); # а теперь приветствует себя и вас

В последней строке возвращаемые значения явно использованы не были, однако для определения суммы

Perl сначала должен вычислить все ее слагаемые, поэтому подпрограмма была вызвана дважды.

Вот пример с использованием более одного параметра:

print "$_, $_[!]!\n";

say("hello","world"); #

опять hello world

say ("goodbye", "cruel world"); # goodbye cruel world -

популярная фраза из фильмов

Избыточные параметры игнорируются, т.е. если вы никогда не заглядываете в

$_ [ 3 ], языку Perl это абсолютно все равно. Недостающие параметры также игнорируются, и если вы попытаетесь обратиться за пределы массива @_, как и любого другого массива, то просто получите в ответ undef.

Переменная @_ является локальной для подпрограммы. Если для @_ установлено глобальное значение, то оно перед вызовом подпрограммы сохраняется, а после возврата из подпрограммы восстанавливается. Это также означает, что подпрограмма может передавать аргументы в другую подпрограмму, не боясь "потерять" собственную переменную @_; вложенный вызов подпрограммы точно так же получает собственную переменную @_.

Давайте вернемся к программе сложения а и

b из предыдущего раздела. Вот подпрограмма, которая складывает любые два значения, а именно два значения, передаваемые в нее как параметры:

return $_ + $_[!];

print add_two(3,4);

# выводит значение 7 = add_two(5,6); # $с получает значение 11

Давайте обобщим эту подпрограмму. Что, если нам нужно сложить

3, 4 или 100 значений? Это можно было бы сделать с помощью цикла, например: # инициализировать сумму

foreach $_ (@_) {

# прибавить все элементы } = add(4,5,6); # складывает 4+5+6=15 и присваивает переменной $а print add(1,2,3,4,5); # выводит 15 print add (1..5); # тоже выводит 15, потому что список 1..5 раскрывается

Что, если бы переменная с именем

$sum использовалась у нас раньше? При вызове подпрограммы add мы просто потеряли бы ее значение. В следующем разделе мы увидим, как избежать этого.

Локальные переменные в функциях

Мы уже говорили о переменной @_ и о том, как для каждой подпрограммы, вызываемой с параметрами, создается локальная копия этой переменной. Вы можете создавать собственные скалярные переменные, переменные-массивы и хеш-переменные, которые будут работать точно так же. Это делается с помощью операции ту, которая получает список имен переменных и создает их локальные версии (или реализации, если вам так больше нравятся). Вот снова функция

add, на этот раз построенная на основе операции ту: ($sum); # сделать $sum локальной переменной $sum =0; # инициализировать сумму

foreach $_ (@_) (

прибавить все элементы последнее вычисленное выражение: сумма всех элементов }

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

$sum сохраняется и создается совершенно новая переменная с именем $sum (и значением undef). При выходе из подпрограммы Perl отбрасывает эту локальную переменную и восстанавливает предыдущее (глобальное) значение. Эта схема работает даже в том случае, если переменная $sum в текущий момент является локальной переменной, взятой из другой подпрограммы (той, которая вызывает данную подпрограмму, или той, которая вызывает ту, которая вызывает данную подпрограмму, и т.д.). Переменные могут иметь много вложенных локальных версий, но одновременно разрешается обращаться только к одной из них.

Вот способ создания списка всех элементов массива, значения которых превышают число

100:

sub bigger_than_100 (

временная для хранения возвращаемого значения foreach $_ (@_) ( # проход по списку аргументов if ($_ > 100) ( # подходит?

push(@result,$_);

# прибавить })

return Oresult; #

}

Что, если бы нам понадобились все элементы, значения которых превышают

50, а не 100? Пришлось бы отредактировать программу, заменив 100 на 50. А если бы нам было нужно и то, и другое? В этом случае следовало бы заменить 50 или 100 ссылкой на переменную. Тогда программа выглядела бы так:

sub bigger_than (

my($n,@values); #

создать локальные переменные ($n,@values) = @_; # выделить из списка аргументов значение предела

# и элементы массива

my (@result); # временная переменная для хранения возвращаемого значения

foreach $_ (@values) (#

проход по списку аргументов if ($_ > $n) { # подходит?

push(Oresult,$_);

# прибавить }) возвратить окончательный список }

# примеры вызова

:

@new = bigger_than(100,@list); # @new

содержит все значения Olist > 100 @this " bigger_than(5,l,5,15,30); # @this содержит значение (15,30)

Обратите внимание: в этот раз мы использовали еще две локальные переменные, чтобы присвоить имена аргументам. Это

- весьма распространенная практика, потому что гораздо удобнее (да и безопаснее) использовать переменные $п и $values, чем указывать $_ и @_.

В результате выполнения операции my создается список, который можно использовать в левой части операции присваивания. Этому списку можно присваивать начальные значения для каждой из вновь созданных переменных. (Если значения списку не присвоить, то новые переменные вначале получат значение

undef, как и любая другая переменная.) Это значит, что мы можем объединить первые два оператора этой подпрограммы, т.е. операторы

my($n,@values) ;

@_; # выделить из списка аргументов значение предела # и элементы массива

заменить на

my($n,@values)= @_;

Этот прием очень распространен в

Perl. Локальным переменным, не являющимся аргументами, можно таким же способом присваивать литеральные значения, например: # инициализировать локальную переменную

Имейте в виду, что, несмотря на наличие объявления, my в действительности представляет собой выполняемую операцию. Стратегия правильной работы в

Perl состоит в том, что все операции my должны быть размещены в начале определения подпрограммы, до того, как начинается реализация основных выполняемых в ней действий.

Полулокальные переменные, созданные при помощи функции

local
Perl существует еще один способ создания "частных" локальных переменных - с помощью функции local. Важно понять различия между my и local. Например:

$values = "original";

local ($value) = "temporary";

> sub telime {

print "Current value is $value\n";

На выходе получаем следующее:

Current value is original Current value is temporary Current value is original

Если бы вместо

local мы использовали my, то локальный вариант переменной $value был бы доступен только в пределах подпрограммы spoof (). При использовании функции local, как видно по результатам программы, это локальное значение не совсем локальное; оно доступно и во всех остальных подпрограммах, вызываемых из spoof (). Общее правило таково: локальные переменные доступны для функций, вызываемых из того блока, в котором эти функции объявлены.

Операцию my можно использовать только для объявления простых скалярных переменных, переменных-массивов и хеш-переменных с буквен-но-цифровыми именами, тогда как для переменной

local такие ограничения не установлены. Кроме того, встроенные переменные Perl, такие как $_, и @argv, с помощью my объявлять нельзя, а вот с local они работают прекрасно. Поскольку $_ используется в большинстве Perl-программ, то будет, вероятно, разумным помещать строку

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

$_ для собственных нужд. Это гарантирует сохранность предыдущего значения и его автоматическое восстановление при выходе из подпрограммы.

На более серьезном уровне программирования вам, возможно, нужно будет знать, что переменные, создаваемые функцией

local, - это, по сути дела, замаскированные глобальные переменные, т.е. значение глобальной переменной сохраняется и временно заменяется локально объявленным значением. local, a my, потому что эта операция действует быстрее и надежнее.

Создаваемые операцией my() переменные файлового уровня

Операцию my

() можно также использовать на внешнем уровне программы, вне всех подпрограмм и блоков. Хотя в результате не будет получена "локальная" переменная в описанном выше смысле, это может оказаться достаточно полезным, особенно при использовании в сочетании с Рет\-прагмой* Прагма - это директива компилятора. Среди этих директив - директивы задания целочисленных арифметических операций, перегрузки числовых операций, запрашивания дополнительных текстовых предупреждений и сообщений об ошибках. Эти директивы описаны в главе 7 книги Programming Perl и на man-странице perlmodlib(l).

Если поставить эту прагму в начало файла, то вы больше не сможете использовать переменные (скалярные, массивы и хеши), сначала не "объявив" их. Объявляются они с помощью операции my

() следующим образом:

my $а; # сначала значение

undef my @b = qw(fred barney betty); # присвоить начальное значение

push @b, qw(wilma); #

разве можно забыть про Вильму? @с = sort @b; # HE КОМПИЛИРУЕТСЯ

Во время компиляции последний оператор будет помечен флагом ошибки, потому что он обратился к переменной, не объявленной ранее с помощью операции my (т.е. @с). Другими словами, ваша программа даже не начнет работать до тех пор, пока не будут объявлены все используемые переменные.

Преимущества принудительного объявления переменных таковы:

Ваши программы будут работать несколько быстрее (обращение к переменным, созданным с помощью my, производится несколько быстрее, чем к обычным переменным).* Гораздо быстрее будут выявляться ошибки набора, потому что вы больше

не сможете случайно обратиться к несуществующей переменной

$ freed,

когда вам будет нужна переменная

$fred.

По этой причине многие программисты, пишущие на

Perl, автоматически начинают каждую новую программу прагмой use strict.

Упражнения

Напишите подпрограмму, которая будет принимать в качестве аргумента числовое значение от 1 до 9 и возвращать английское название этого числа (т.е. one, two и т.д.). Если значение не принадлежит указанному диапазону, подпрограмма должна возвратить вместо имени исходное число. Проверьте работу подпрограммы, введя какие-нибудь данные. Для вызова этой подпрограммы вам, наверное, придется написать какой-то код. (Совет: эта подпрограмма не должна выполнять никакого ввода-вывода.) Взяв подпрограмму из предыдущего упражнения, напишите программу, которая будет брать два числа и складывать их, выдавая результат в формате Two plus two equals four. (He забудьте начать первое слово с заглавной буквы!) Модернизируйте эту подпрограмму так, чтобы она возвращала названия от negative nine до negative one и zero (т.е. принимала числа из диапазона от -9 до -1 и нуль). Проверьте, как она будет работать в программе из упражнения 2. "Обычная переменная" в этом случае - переменная пакета (поэтому $х - это, по сути дела, $main:: х). Переменные, созданные с помощью my (), ни в один пакет не входят.
  • Tutorial

В Perl заложено огромное количество возможностей, которые, на первый взгляд, выглядят лишними, а в неопытных руках могут вообще приводить к появлению багов. Доходит до того, что многие программисты, регулярно пишущие на Perl, даже не подозревают о полном функционале этого языка! Причина этого, как нам кажется, заключается в низком качестве и сомнительном содержании литературы для быстрого старта в области программирования на Perl. Это не касается только книг с Ламой, Альпакой и Верблюдом («Learning Perl », «Intermediate Perl » и «Programming Perl ») - мы настоятельно рекомендуем их прочитать.

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

Как работают функции Perl?

В большинстве языков программирования описание функции выглядит примерно так:
function myFunction (a, b) { return a + b; }
А вызывается функция так:
myFunction(1, 2);
На первый взгляд всё просто и понятно. Однако вызов этой функции в следующем виде:
myFunction(1, 2, 3); … в большинстве случаев приведёт к ошибкам, связанным с тем, что в функцию передано неверное количество аргументов.

Функция в Perl может быть записана так:
sub my_sub($$;$) : MyAttribute { my ($param) = @_; }
Где $$;$ - это прототип, а MyAttribute - это атрибут. Прототипы и атрибуты будут рассмотрены далее в статье. А мы пока рассмотрим более простой вариант записи функции:
sub my_sub { return 1; }
Здесь мы написали функцию, которая возвращает единицу.

Но в этой записи не указано, сколько аргументов принимает функция. Именно поэтому ничего не мешает вызвать её вот так:
my_sub("Туземец", "Бусы", "Колбаса", 42);
И всё прекрасно выполняется! Это происходит потому, что в Perl передача параметров в функцию сделана хитро. Perl славится тем, что у него много так называемых «специальных» переменных. В каждой функции доступна специальная переменная @_, которая является массивом входящих параметров.

Поэтому внутри функции мы можем поместить входные параметры в переменные так:
my ($param) = @_;
Это работает и в случае нескольких параметров:
my ($param1, $param2, $param3) = @_;

Очень часто в функциях пишут следующее:
sub my_sub { my $param = shift; ... }
Дело в том, что в Perl многие функции при вызове без аргументов используют переменные по умолчанию. shift же по умолчанию достаёт данные из массива @_. Поэтому записи:
my $param = shift; … и
my $param = shift @_; … совершенно эквивалентны, но первая запись короче и очевидна для Perl-программистов, поэтому используется именно она.

Shift можно использовать и для получения нескольких параметров, в том числе комбинируя в одно списочное присваивание:
my ($one, $two, $three) = (shift, shift, shift);
Другая запись:
my ($one, $two, $three) = @_; … работает точно так же.

А теперь внимание! Грабли, на которые рано или поздно наступает каждый Perl-программист:
sub my_sub { my $var = @_; print $var; }
Если вызвать данную функцию как my_sub(1, 2, 3) в $var мы внезапно получим не 1, а 3. Это происходит потому, что в данном случае контекст переменной определяется как скалярный, а в Perl массив в скалярном контексте возвращает свой размер, а не первый элемент. Чтобы исправить ошибку, достаточно взять $var в скобки, чтобы контекст стал списочным. Вот так:
sub my_sub { my ($var) = @_ }
И теперь, как и ожидалось, при вызове my_sub(1, 2, 3) в $var будет 1.

Кроме того, в Perl параметры передаются по ссылке. Это значит, что мы можем внутри функции модифицировать параметры, которые в неё переданы.

Например:
my $var = 5; my_sub($var); print $var; sub my_sub { # вспоминаем, что доступ к элементам массива выполняется в скалярном контексте # т. е. доступ к нулевому элементу массива @arr будет выглядеть как $arr, то же самое и с # @_. $_++; # $_ - первый элемент массива @_. }
Результат будет 6. Однако в Perl можно сделать в каком-то роде «передачу по значению» вот так:
my $var = 5; my_sub($var); print $var; sub my_sub { my ($param) = @_; $param++; }
А вот теперь результат будет 5.

И последние два нюанса, которые очень важны. Perl автоматически возвращает из функции результат последнего выражения.

Возьмём код из предыдущего примера и немного его модифицируем:
my $var = 5; my $result = my_sub($var); print $result; sub my_sub { my ($param) = @_; ++$param; }
Это будет работать точно так же, как если бы в последней строке функции был явный возврат значения:
return ++$param;
Функция вернёт 6.

И ещё одна особенность: если в теле функции вызывается другая функция с использованием амперсанда и без скобок, то внутренняя функция получает на вход параметры той функции, в теле которой она вызывается. Т. е. массив @_ будет автоматически передан из внешней функции во внутреннюю. Это может привести к неочевидным багам.
use strict; use Data::Dumper; my_sub(1, 2, 3); sub my_sub { &inner; } sub inner { print Dumper \@_; }
Результат:
$VAR1 = [
1,
2,
3
];

Однако, если явно указать (с помощью пустых скобок), что функция вызывается без параметров, то всё в порядке:
sub my_sub { &inner(); }
И вывод будет выглядеть вот так:
$VAR1 = ;

Впрочем, вызовы функций с использованием амперсанда используются очень редко и в коде почти не встречаются.

Анонимные функции

Анонимные функции объявляются в месте использования и не получают уникального идентификатора для доступа к ним. При создании они либо вызываются напрямую, либо ссылка на функцию присваивается переменной, с помощью которой затем можно косвенно вызывать эту функцию.

Элементарное объявление анонимной функции в Perl:
my $subroutine = sub { my $msg = shift; printf "I am called with 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
Замыкания использовать полезно, например, когда необходимо получить функцию с уже готовыми параметрами, которые будут в ней сохранены. Или же для генерации функции-парсера, колбеков.

Бесскобочные функции

На наш взгляд, это самый подходящий перевод термина parenthesis-less.

Например, print часто пишется и вызывается без скобок. Возникает вопрос, а можем ли мы тоже создавать такие функции?

Безусловно. Для этого у Perl есть даже специальная прагма - subs. Предположим, нам нужна функция, проверяющая значение переменной на истинность.
use strict; use subs qw/checkflag/; my $flag = 1; print "OK" if checkflag; sub checkflag { return $flag; }
Эта программа напечатает OK.

Но это не единственный способ. Perl хорошо продуман, поэтому, если мы реструктуризируем нашу программу и приведём её к такому виду:
use strict; my $flag = 1; sub checkflag { return $flag; } 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 можно передать любое количество аргументов. И пусть сама функция разбирается, что мы от неё хотели.

Предполагается, что должен быть механизм контроля переданных в функцию аргументов. Эту роль и выполняют прототипы.

Функция 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 (средство статического анализа Perl кода), кстати говоря, ругается на запись вызова функции через амперсанд, то есть такой вариант вызова не рекомендуется.
  • Если функция написана перед вызовом. Если мы сначала вызовем функцию, а потом её напишем, при включённых warnings получим следующее предупреждение:
    main::my_sub() called too early to check prototype at pragmaticperl.pl line 4
Ниже пример правильной программы с прототипами Perl:
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();
В Perl существует возможность узнать, какой у функции прототип. Например:
perl -e "print prototype("CORE::read")"
Выдаст:
*\$$;$

Оверрайд методов

Оверрайд - часто довольно полезная штука. Например, у нас есть модуль, который писал некий 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; };
А ещё лучше, для временного оверрайда используем ключевое слово local:
local *Top::call_me = sub { print "Overrided subroutine called!\n"; return 1; };
Это заменит функцию call_me пакета Top в лексической области видимости (в текущем блоке).
Теперь наш вывод будет выглядеть так:
Overrided subroutine called!
Purrrrfect

Код модуля не меняли, функция теперь делает то, что нам надо.

На заметку: если приходится часто использовать данный приём в работе - налицо архитектурный косяк. Хороший пример использования - добавление вывода отладочной информации в функции.

Wantarray

В Perl есть такая полезная штука, которая позволяет определить, в каком контексте
вызывается функция. Например, мы хотим, чтобы функция вела себя следующим образом:
когда надо возвращала массив, а иначе - ссылку на массив. Это можно реализовать, и
к тому же очень просто, с помощью wantarray. Напишем простую программу для демонстрации:
#!/usr/bin/env perl use strict; use Data::Dumper; my @result = my_cool_sub(); print Dumper @result; my $result = my_cool_sub(); print Dumper $result; sub my_cool_sub { my @array = (1, 2, 3); if (wantarray) { print "ARRAY!\n"; return @array; } else { print "REFERENCE!\n"; return \@array; } }
Что выведет:
ARRAY!
$VAR1 = 1;
$VAR2 = 2;
$VAR3 = 3;
REFERENCE!
$VAR1 = [
1,
2,
3
];

Также хотелось бы напомнить про интересную особенность Perl. %hash = @аrray; В этом случае Perl построит хэш вида ($array => $array, $array => $array);

Посему, если применять my %hash = my_cool_sub(), будет использована ветка логики wantarray. И именно по этой причине wanthash нет.

AUTOLOAD

В Perl одна из лучших систем управления модулями. Мало того что программист может контролировать все стадии исполнения модуля, так ещё существуют интересные особенности, которые делают жизнь проще. Например, AUTOLOAD.

Суть AUTOLOAD в том, что когда вызываемой функции в модуле не существует, Perl ищет функцию AUTOLOAD в этом модуле, и только затем, если не находит, выбрасывает исключение о вызове несуществующей функции. Это значит, что мы можем описать обработчик ситуаций, когда вызывается несуществующая функция.

Например:
#!/usr/bin/env perl use strict; Autoload::Demo::hello(); Autoload::Demo::asdfgh(1, 2, 3); Autoload::Demo::qwerty(); package Autoload::Demo; use strict; use warnings; our $AUTOLOAD; sub AUTOLOAD { print $AUTOLOAD, " called with params: ", join (", ", @_), "\n"; } sub hello { print "Hello!\n"; } 1;
Очевидно, что функций qwerty и asdfgh не существует в пакете Autoload::Demo. В функции AUTOLOAD специальная глобальная переменная $AUTOLOAD устанавливается равной функции, которая не была найдена.

Вывод этой программы:
Hello!
Autoload::Demo::asdfgh called with params: 1, 2, 3
Autoload::Demo::qwerty called with params:

Генерация функций на лету

Допустим, нам нужно написать множество функций, выполняющих примерно одинаковые действия. Например, набор аксессоров у объекта. Написание подобного кода вряд ли кому-то доставит удовольствие:
sub getName { my $self = shift; return $self->{name}; } sub getAge { my $self = shift; return $self->{age}; } sub getOther { my $self = shift; return $self->{other}; }
Это Perl. «Лень, нетерпение, надменность» (Л. Уолл).

Функции можно генерировать. В Perl есть такая штука как тип данных typeglob. Наиболее точный перевод названия - таблица имён. Typeglob имеет свой сигил - «*».

Для начала посмотрим код:
#!/usr/bin/env perl use strict; use warnings; package MyCoolPackage; sub getName { my $self = shift; return $self->{name}; } sub getAge { my $self = shift; return $self->{age}; } sub getOther { my $self = shift; return $self->{other}; } foreach (keys %{*MyCoolPackage::}) { print $_." => ".$MyCoolPackage::{$_}."\n"; }
Вывод:
getOther => *MyCoolPackage::getOther
getName => *MyCoolPackage::getName
getAge => *MyCoolPackage::getAge

В принципе, глоб - это хэш с именем пакета, в котором он определен. Он содержит в качестве ключей элементы модуля + глобальные переменные (our). Логично предположить, что если мы добавим в хэш свой ключ, то этот ключ станет доступен как обычная сущность. Воспользуемся генерацией функций для генерации данных геттеров.

И вот что у нас получилось:
#!/usr/bin/env perl use strict; use warnings; $\ = "\n"; my $person = Person->new(name => "justnoxx", age => "25", other => "perl programmer",); print "Name: ", $person->get_name(); print "Age: ", $person->get_age(); print "Other: ", $person->get_other(); package Person; use strict; use warnings; sub new { my ($class, %params) = @_; my $self = {}; no strict "refs"; for my $key (keys %params) { # __PACKAGE__ равен текущему модулю, это встроенная # волшебная строка # следующая строка превращается в, например: # Person::get_name = sub {...}; *{__PACKAGE__ . "::" . "get_$key"} = sub { my $self = shift; return $self->{$key}; }; $self->{$key} = $params{$key}; } bless $self, $class; return $self; } 1;
Эта программа напечатает:
Name: justnoxx
Age: 25
Other: perl programmer

Атрибуты функций

В Python есть такое понятие как декоратор. Это такая штуковина, которая позволяет «добавить объекту дополнительное поведение».

Да, в Perl декораторов нет, зато есть атрибуты функций. Если мы откроем perldoc perlsub и посмотрим на описание функции, то увидим любопытную запись:
sub NAME(PROTO) : ATTRS BLOCK
Таким образом, функция с атрибутами может выглядеть так:
sub my_sub($$;$) : MyAttr { print "Hello, I am sub with attributes and prototypes!"; }
Работа с атрибутами в 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"; }; } } 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, а потому их синтаксис может меняться в следующих версиях языка.

Статья написана в соавторстве и по техническому материалу от Дмитрия Шаматрина (@justnoxx) и при содействии программистов REG.RU: Тимура Нозадзе (@TimurN), Виктора Ефимова (@vsespb), Полины Шубиной (@imagostorm), Andrew Nugged (@nugged)

Встпвляет пространство имен одного модуля в другой. Это не встроенная функция, а всего лищь метод, наследуемый от модуля (параметр МОДУЛЬ), которому необходимо экспортировать свои имена (параметр СПИСОК) в другой модуль.

Import МОДУЛЬ СПИСОК

local

Функция local() используется для объявления и инициализации одной или нескольких переменных:

Local EXPR local ($myvar, , %myhash); local $pi = 3.14159; local ($pi, $exp) = (3.14159, 2.71828);

но, в отличие от функции my() она создает не локальные переменные, а временные значения для глобальных переменных внутри:

  • подпрограммы;
  • заключенного в фигурные скобки блока операторов;
  • выражения, пареданного на выполнение функции eval();
  • файла;

В зависимости от того, в каком месте вызвана для объявления переменных сама функция local(). Если функция local() применяется для описания нескольких переменных, они должны быть заключены в скобки. Если глобальня перемнная, объявленная при помощи этой функции, ранее встречалась до объявления и имела некоторое значение, то это значение сохраняется в скрытом стеке и восстанавливается после выхода соответственно из подпрограммы, блока, функции eval() или файла. Переменная, объявленная при помощи функции local(), или, точнее, ее временное значение, доступна для любой функции, вызванной внутри подпрограммы, блока, функции eval() или файла, в которых сделано объявление. Такую переменную называют динамической, а ее область видимости - динамической областью видимости. В названии отражается тот факт, что область видимости переменной динамически изменяется с каждым вызовом функции, получающей доступ к этой переменной.

Пример:

my

Функция my() используется для объявления одной или нескольких переменных локальными:

и ограничивает их область действия:

  • подпрограммой;
  • заключенным в фигурные скобки блоком операторов;
  • выражением, пареданным на выполнение функции eval();
  • файлом, в зависимости от того, в каком месте вызвана для объявления переменных сама функция my().

Если выражение EXPR содержит список переменных, то он должен быть заключен в скобки:

My ($myvar, @mylist, %myhash);

Одновременно с объявлением переменные могут быть инициализированны:

My $pi = 3.14159; my ($pi, $exp) = (3.14159, 2.71828);

Переменные, объявленные при помощи функции my(), доступны в своей области действия только для подпрограмм, определенных в этой области. Для подпрограмм, определенных за ее пределами, они недоступны. Такие переменные называются лексическими, а саму область видимости - лексической или статической областью видимости.

Пример:

Sub f1{ local ($x) = "aaaa"; my($y) = "bbbb"; print("f1: x = $xn"); print("f1: y = $ynn"); f2(); print("f1: x = $xn"); print("f1: y = $ynn"); } sub f2{ print("f2: x = $xn"); print("f2: y = $ynn"); $x = "cccc"; $y = "dddd"; print("f2: x = $xn"); print("f2: y = $ynn"); } f1;

Результатом выполнения данного примера будет следующий вывод:

F1: x = aaaa f1: y = bbbb f2: x = aaaa f2: y = f2: x = cccc f2: y = dddd f1: x = cccc f1: y = bbbb

Как видно из приведенного результата, функция f2() не имеет доступа к переменной $y, объявленной при помощи функции my() внутри функции f1(), и,напртив, имеет доступ к переменной $x, объявленной внутри f1() при помощи функции local().

package

Определяет отдельное глобальное пространство имен (пакет): все неопределенные динамические идентификаторы (включая те, которые объявлены через local(), но не my()) будут храниться в нем. Для доступа к ним вне пакета следует указывать префикс, представляющий имя пакета с последующими двумя символами двоеточий "::". Область видимости переменных пакета распространяется до конца блока операторов, в котором расположен пакет или до нового объявления пакета. Если опущено имя пакета, то предписывает, чтобы все индентификаторы были определены, включая имена функций.

Package [ИМЯ_ПАКЕТА]

use

Загружает модуль во время компиляции; если модуль не доступен, то компиляция всей программы прекращается.

Use МОДУЛЬ СПИСОК use МОДУЛЬ use МОДУЛЬ ВЕРСИЯ СПИСОК use ВЕРСИЯ

Расмотрены наиболее интересные, по мнению автора, особенности языка 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).

Множественное наследование

Увы, как показыват практика и очень умные книги очень умных людей - если у вас в приложении появилась необходимость множественного наследования, то это приложение можно описать примерно следующим стишком.

ваш код сегодняшний коллега напоминает даунхилл среди деревьев и говнища велосипед и костыли

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.

И снова говорю вам ЗДРАВСТВУЙТЕ! Если вы это читаете, значит продержались, к моей большой радости, до 9-го шага. Сегодня мы будем рассматривать достаточно трудные моменты языка. Если кому-то что-то будет непонятно (или было непонятно в предыдущих шагах), то пишите мне и я постраюсь объяснить. Если писем с "непонятками" на какой-то шаг наберётся определённое кол-во, то я буду переписывать этот шаг в более понятной форме. Поехали (запил кофеём)!

В Perl нет разделения на процедуры и функции, поэтому мы будем использовать общее определение - "подпрограмма". Мы научимся в этом шаге создавать свои подпрограммы, рассмотрим некоторые тонкости вызова встроеных функций Perl , коснёмся вопроса "Что такое указатель и зачем он нужен немерянно крутому программеру, которым я хочу стать?" и узнаем о пользе рукурсии в деле программерском.

Для применения подпрограммы ее необходимо определить либо в текущем модуле (файле), либо во внешнем модуле (файле). Объявляются подпрограммы с помощью ключевого слова sub . Чтобы использовать подпрограмму объявленную во внешнем модуле, нужно перед её использованием указать интерпретатору где её искать следющим образом use имя_модуля qw(подрограмма) или require имя_файла . Как происходит вызов подпрограмм посмотрим на примере

#!/usr/bin/perl -w sub test { print "Привет из подпрограммы!\n"; } &test();

Cимвол & означает, что после него идёт вызов подпрограммы. Т.е. в данном случаем вызов подпрограммы test() . Но можно даже обойтись вообще без него. Теперь ещё одна тонкость. Обойтись можно и без скобок. Выглядеть это будет так:

#!/usr/bin/perl -w sub test { print "Привет из подпрограммы!\n"; } test;

Причем обойтись без скобок можно не только в тех подпрограммах, которые создаём мы, но и при вызове функций Perl . Скобки нужны только в том случае, если передаваемые функции параметры могут вызвать неоднозначность трактования. Мы уже не раз "своевольничали", не пользуясь скобками. Функция print правильно пишется так - print("Привет из подпрограммы!\n") . Теперь о том, как передаются подпрограммам параметры. Каждая подпрограмма при запуске получает массив @_ , в котором содержатся параметры. Ещё один пример:

#!/usr/bin/perl -w sub test { print $_; } &test("Привет из подпрограммы!\n");

Или так:

#!/usr/bin/perl -w sub test { print $_; } test "Привет из подпрограммы!\n";

Ощутили? Поехали дальше:) Подпрограммы могут быть объявленны как перед их вызовом, так и после. Подпрограммы могут возвращать какое-то значение. Для этого служит оператор return , он возвращает идущее за ним значение.

#!/usr/bin/perl -w sub test { return "Я вернулся:)"; } print &test();

Можно обходится и без него. Тогда Perl вернёт значение последнего оцененного выражения. Ещё о передаче подпрограмме параметров. В Perl все переменные передаются по ссылке. Поэтому нет смысла передавать подпрограмме указатель на переменную. При передаче параметром массива, мы фактически передаём ту же ссылку на первый элемент массива, поэтому можно так же, как с переменной, не указывать специально, что мы передаём указатель. Но я всё же покажу как это делается. Указатель - это переменная содержащая адрес другой переменной. Чтобы объявить указатель надо перед переменной поставить символ * . Примерно так:

*POINTER;

Чаще всего указатели служат для хранения хэндлера. Что такое хэндлер мы скоро рассмотрим. Операция взятия адреса в Perl обозначается сиволом "\ ". Небольшой пример:

#!/usr/bin/perl -w $x=0; print \$x;

Как видите мы получили не содержимое переменной, а её адрес - 0x18326bc (у вас может быть другой).

А сейчас рассмотрим как вынести свои подпрограммы в отдельный файл. Для этого достаточно сохранить свою подпрограмму в файл (предположим "test.pl"), а в том файле, в котором стоит вызов подпрограммы написать require "test.pl" . Как делать модули мы узнаем позже.

Обещаная рекурсия. Рекурсивной называется подпрограмма, способная вызывать саму себя. Что это даёт? Например, возможность обработки древовидных структур, как то - дерево каталогов, ссылочная связь гипертекстовых документов и т.п. Ни с сетью, ни с файлами мы пока работать не умеем. Научимся чуть позже. Поэтому пока будем эмулировать бурную деятельность:)

#!/usr/bin/perl -w $cnt=0; sub recurs { my $level = $_; $cnt++; print "$level\n"; if($cnt