Некоторые сведения о Perl 5/Пакеты, библиотеки и модули

← Функции и процедуры Глава Объектно-ориентированное программирование в Perl →
Пакеты, библиотеки и модули


Пакеты

править

Пакет — это пространство имен для некоторой части программы. Каждый фрагмент кода Perl относится к некоторому пакету.

Чтобы объявить пакет, используется конструкция

package <имя-пространства-имен> [<версия>];

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

  • процедуры;
  • блока;
  • строки, переданной функции eval();
  • исходного файла.

Все идентификаторы, встречающиеся после пакета, автоматически связываются с ним, кроме локальных переменных, созданных с помощью my().

Пространство имен можно многократно менять в разных точках программы. По умолчанию предполагается, что основная программа всегда начинается с объявления пакета package main;. Таким образом, все глобальные переменные неявно находятся в пакете main.

К переменным пакета main мы можем обращаться без всяких дополнительных префиксов, но для обращения к объектам других пакетов нужно использовать следующий синтаксис

<идентификатор-типа><пространство-имен>::<имя-объекта>
# Например
$PackageName::var;  # Обращение к скаляру в пространстве PackageName

# Для пространства main допустимо указывать пакет явно, но это
# в общем то бесполезно, если это пространство уже действует.
# Следующие три обращения направлены на одну и ту же переменную в пространстве
# main.
$var;
$main::var;
$::var;

С помощью специальной лексемы __PACKAGE__ можно узнать какое пространство имен применяется в некоторой точке программы.

Таблица символов пакета

править

С каждым пакетом связана отдельная таблица символов. Она представляет собой хеш-массив, имя которого образовано из имени пакета, за которым следуют два двоеточия. Например, таблица символов для пакета main хранится в хеш-массиве %main::.

Ключами этого хеш-массива являются идентификаторы переменных, определенных в пакете, а значениями — typeglob-ссылки, указывающие на гнездо, состоящее из одноимённых переменных разных типов.

Таблица символов может использоваться для создания псевдонимов, так как typeglob-ссылки обладают следующими свойствами:

*alias = *object;
# alias становится псевдонимом для всех переменных одного гнезда
# таблицы: $object, @object, %object, &object.

# Можно создать псевдоним только для конкретного объекта
*alias = \$object;  # Псевдоним для скаляра $object

Функция из следующего примера позволяет распечатать таблицу символов.

#
# Аргументы:
#   $1  - имя пространства для печати
#  \%2  - ссылка на хеш-массив пространства имен
#
# Пример:
#    printNamespace ("main", %main::);
#
sub printNamespace ($\%) {
    my ($spaceName, $space) = @_;
    my ($key, $item);
    print "Symbols in the '$spaceName' namespace:\n";
    for $key (sort keys %$space) {
        local *glob = $$space{$key};
        print "  scalar: \$$key = $glob\n" if defined $glob;
        if (@glob) {
            print "  array '\@$key':\n";
            for $item (0..$#glob) {
                print "    \$$key [$item] = $glob[$item]\n";
            }
        }
        if (%glob) {
            print "  hash '\@$key':\n";
            for $item (sort keys %glob) {
                print "    \$$key {$item} = $glob{$item}\n";
            }
        }
        print "  routine '$key()'\n" if defined &glob;
    }
}

package test1;

$scalar = 4;
@array = (1, 2, 3);
%hash = (name => "Larry", surname => "Wall");
sub func { return 0 }

package test2;

$scalar = 4;
@array = (1, 2, 3);
%hash = (name => "Larry", surname => "Wall");
sub func { return 0 }

package main;

printNamespace ("test1", %test1::);
printNamespace ("test2", %test2::);

Результат вывода

Symbols in the 'test1' namespace:
  array '@array':
    $array [0] = 1
    $array [1] = 2
    $array [2] = 3
  routine 'func()'
  hash '@hash':
    $hash {name} = Larry
    $hash {surname} = Wall
  scalar: $scalar = 4
Symbols in the 'test2' namespace:
  array '@array':
    $array [0] = 1
    $array [1] = 2
    $array [2] = 3
  routine 'func()'
  hash '@hash':
    $hash {name} = Larry
    $hash {surname} = Wall
  scalar: $scalar = 4

В этом примере мы создали два пакета test1 и test2, в каждом из которых мы объявили скаляр, массив, хеш и функцию.

В таблицу символов пакета, отличного от main, входят только идентификаторы, начинающиеся с буквы или символа нижнего подчеркивания. Все остальные идентификаторы относятся к пакету main. Кроме них к main также относятся: STDIN, STDOUT, STDERR, ARGV, ARGVOUT, ENV, INC, SIG. В этих случаях не обязательно указывать пространство имен из другого пространства.

Конструктор и деструктор пакета

править

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

Конструктор пакета — это процедура, оформленная в специальном блоке BEGIN, которая выполняется сразу после своего определения до завершения компиляции оставшейся части программы.

Деструктор пакета — это процедура, оформленная в специальном блоке END, которая выполняется перед выгрузкой пакета.

BEGIN { <операторы> }
END { <операторы> }

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

Следующий пример демонстрирует, как вызываются конструкторы и деструкторы пакетов.

BEGIN {
    print "Package " . __PACKAGE__ . " are loading ...", "\n";
}

END {
    print "Package " . __PACKAGE__ . " are unloading ...", "\n";
}

END {
    print __PACKAGE__ . ": additional destructor.", "\n";
}

package test1;

BEGIN {
    print "Package " . __PACKAGE__ . " are loading ...", "\n";
}

BEGIN {
    print __PACKAGE__ . ": additional consturctor.", "\n";
}

END {
    print "Package " . __PACKAGE__ . " are unloading ...", "\n";
}

Результат вывода

Package main are loading ...   
Package test1 are loading ...  
test1: additional consturctor. 
Package test1 are unloading ...
main: additional destructor.   
Package main are unloading ...

Автозагрузка

править

При попытке обратиться к функции, которая еще не определена в пакете, интерпретатор завершает работу с выдачей сообщения об ошибке. Если в пакете определить функцию со специальным именем AUTOLOAD, то интерпретатор будет перенаправлять вызов на нее с сохранением передаваемых параметров. Кроме того, внутри этой функции будет определен скаляр $AUTOLOAD, в котором будет хранится имя вызываемой функции.

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

sub AUTOLOAD {
    print "Called '$AUTOLOAD': @_\n";
}

testCall(1, 2 ,4);
undefinedFunc(1, "a", 3.1415, "b");

Результат

Called 'main::testCall': 1 2 4
Called 'main::undefinedFunc': 1 a 3.1415 b

Библиотека

править

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

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

Библиотеки появились в Perl с 4-й версии языка и являются классическим методом. Библиотека — это пакет, областью действия которого является отдельный файл. Другими словами, библиотека Perl — это исходный файл, составленный на языке Perl, первая строка которого вводит пространство имен:

package package_name;

Имя библиотечного файла обычно имеет расширение .pl.

Основная программа загружает библиотеки при помощи директивы компилятора require.

Загрузка библиотеки

править

Директива

require [<выражение>]

служит для загрузки функций из внешних библиотек во время выполнения. Если <выражение> отсутствует, то вместо него будет использоваться переменная $_.

Если <выражение> является числом, то вызов соответствует требованию, что для выполнения данного сценария необходим интерпретатор perl с номером версии, не меньшим, чем указано. Самую меньшую версию, которую можно указать, это 5.005. Более ранние интерпретаторы будут прерывать исполнение с выдачей сообщения об ошибке.

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

sub require {
    my ($filename) = @_;
    return 1 if $INC{$filename};
    my ($realfilename, $result);
    ITER: {
        foreach $prefix (@INC) {
            $realfilename = "$prefix/$filename";
            if (-f $realfilename) {
                $result = do $realfilename;
                last ITER;
            }
        }
        die "Can't find $filename in \@INC"; 
    }
    die $@ if $@;
    die "$filename did not return true value" unless $result;
    $INC{$filename} = $realfilename;
    return $result;
}

Специальный встроенный массив @INC содержит имена каталогов, в которых следует искать сценарии Perl, подлежащие выполнению в конструкциях:

  • do <файл>;
  • require;
  • use.

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

Специальный встроенный хеш-массив %INC содержит по одному элементу для каждой загруженной библиотеки с помощью do и require. Ключом является имя файла, в том виде, в котором оно было указано, а значением — полный путь к нему. Хеш-массив %INC защищает программу от повторной загрузки библиотеки, так как он проверяется перед включением.

Обычно библиотечные файлы имеют расширение .pl в своем имени. Так как в Perl есть операция конкатенации ., чтобы не путать интерпретатор, следует всегда передавать имена файлов в кавычках:

require "mylib.pl";

Если файл не имеет расширения, то по умолчанию предполагается, что это модуль, которые имеют расширение .pm.

Рекомендации по созданию библиотечных файлов

править

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

  • Создайте каталог для хранения библиотечных файлов и помещайте файлы в него по мере роста библиотеки.
  • Типовая струтура библиотечного файла имеет вид:
    package myspace;
    ...
    # Процедуры, функции и другое
    ...
    
    1;
    
    В конце файла следует поместить строку 1;, которая нужна для реализации вызова do <файл>: несложно догадаться, что если do удается распарсить файл целиком, то наружу нужно вернуть ИСТИНУ, которая как раз возвращается единицей.
  • Библиотеки следует включать в основном директивой require.
  • Пути поиска пользовательских библиотек можно внедрять в программу по-разному:
    • Раньше @INC обычно редактировался в конструкторе пакета как то так:
      BEGIN { unshift(@INC, <список>) }
      
      Сейчас рекомендуется использовать директиву lib (см. ниже).
    • Можно передавать пути к каталогам через опцию -I.

Модуль

править

Модули являются дальнейшим развитием библиотек и появились начиная с Perl 5. Модуль — это библиотека, оформленная особым образом, которая определяет свои правила включения.

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

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

С появлением модулей появилась и новая директива для их подключения к основной программе — use().

Директива use()

править

Директива

use <модуль> [<список>]
use <версия>

служит для того же самого, что и require(), но ориентирована на загрузку модулей. Она автоматически импортирует имена функций и переменных в основное пространство имен текущего пакета. Для этого в импортируемом модуле неявно вызывается метод import(). Этот метод должен быть определен в экспортирующем модуле, либо он должен его унаследовать у модуля Exporter. Большинство модулей не имеют своего метода import(), поэтому они наследуют его из модуля Exporter.

Логику работы директивы use можно описать строкой:

BEGIN { require <модуль>; import <модуль> <список>; }

Здесь <модуль> это слово без суффиксов, не заключенное в кавычки. Если аргументом является число <версия>, то оно будет обозначать номер версии интерпретатора perl. Если номер версии текущего интерпретатора меньше указанного, то интерпретатор завершит работу с сообщением об ошибке.

Если при импорте модуля указан <список>, то будут импортироваться символы, указанные в этом списке, иначе будут импортироваться символы из массива @EXPORT, определенном в самом модуле.

Создание и подключение модуля

править

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

# Файл: TestModule.pm
package TestModule;                          # Пространство модуля
require Exporter;                            # Импортируем метод import()
@ISA = qw{ Exporter };                       # Для наследования из @ISA
@EXPORT = qw{ test printContent };           # Символы, экспортируемые по умолчанию
@EXPORT_OK = qw{ $scalar @array %hash };     # Символы, экспортируемые по требованию

# Функции модуля
sub test {
    my ($element, $counter);
    @array = @_;
    $scalar = $#array + 1;
    foreach $element (@array) {
        $hash{"$element"} = ++$counter;
    }
}

sub printContent {
    foreach $key (sort keys %hash) {
       print __PACKAGE__ . "::\$hash {$key} = $hash{$key}\n";
    }
    print __PACKAGE__ . "::Scalar = $scalar\n";
}

1;

Первые пять строк (начиная со второй) являются стандартными для любого модуля:

  • package TestModule; Вводит пространство имен модуля TestModule, исходный файл которого имеет имя TestModule.pm.
  • require Exporter; Мы подключаем библиотеку Exporter, чтобы не писать метод import().
  • @ISA = qw{ Exporter }; С каждым пакетом ассоциируется свой массив @ISA, включающий имена других пакетов, представляющих классы. Если интерпретатор наталкивается на метод, не определенный в текущем пакете, он просматривает массив @ISA, чтобы найти его среди перечисленных пакетов. В нашем примере достаточно указать только модуль Exporter.
  • @EXPORT = qw{ test printContent }; Массив символов, которые импортируются из модуля TestModule неявно. В данном случае мы всегда неявно импортируем обе функции, которые определены в модуле.
  • @EXPORT_OK = qw{ $scalar @array %hash }; Массив символов, которые мы разрешаем импортировать из модуля TestModule по требованию списком. В данном примере мы разрешаем импортировать все глобальные переменные модуля. На практике, обычно, модуль имеет свои внутренние переменные, которые он никому не раскрывает.

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

# Файл: main.pl
use TestModule qw{ :DEFAULT $scalar };

test one, two, three, four;
printContent;
print __PACKAGE__ . ": TestModule::Scalar = $scalar\n";

Первой строкой use TestModule qw{ :DEFAULT $scalar }; мы подключаем модуль TestModule. Кроме имени модуля, мы передаем список того, что хотим импортировать в наше приложение. В нашем примере мы передаем спецификацию :DEFAULT, которая требует импортировать все, что перечислено в массиве @EXPORT, а также дополнительно мы просим импортировать символ $TestModule::scalar.

Далее мы вызываем функцию модуля test(), которая инициализирует внутренние массивы модуля переданными значениями, а затем мы вызываем функцию печати printContent(), которая печатает содержимое хеш-массива модуля. Благодаря тому что мы импортировали символ $TestModule::scalar, мы можем им пользоваться в нашем приложении, как будто он существует в пространстве main.

Вызывать нашу программу мы должны с опцией -I, так как наш модуль не лежит в стандартном для модулей месте. Таким образом, вызов должен быть таким:

$ perl -I$(pwd) ./main.pl

Результат работы программы представлен ниже

TestModule::$hash {four} = 4
TestModule::$hash {one} = 1  
TestModule::$hash {three} = 3
TestModule::$hash {two} = 2  
TestModule::Scalar = 4       
main: TestModule::Scalar = 4

Директива no()

править

Директива

no <имя-модуля> <список>

является противоположной по смыслу директиве use(). Она отменяет действия, связанные с импортом, вызывая метод unimport <имя-модуля> <список>.

Не все библиотечные модули реагируют на эту директиву. Некоторые другие директивы не реагируют на no, потому что с логической точки зрения это было бы странным. Например, к такой директиве относится subs (см. ниже): её действие длится с момента определения и до конца файла.

Тонкости экспорта модулей

править

Чтобы директива use отрабатывала правильно, первой строкой модуль должен вводить пространство имен директивой package, причем имя пространства всегда должно совпадать с именем файла (без учёта расширения). Похожий подход используется в языке Java, где имя файла совпадает с именем первого публичного класса внутри файла исходного кода. В таком случае директива всегда неявно пытается найти и вызвать функцию import() внутри модуля.

Например, если бы у нас существовал собственный не вложенный модуль с именем MyModule, то программа, вызывающая его, должна в служебном массиве @INC хранить директорию, в которой находится файл MyModule.pm, а директива use внутри вызывающего кода могла бы выглядеть так:

# Код вызывающей программы
# ...
# Мы передаем модулю список аргументов.
use MyModule qw{ arg1 arg2 arg3 };
# ...
no MyModule qw{ arg1 arg2 arg3 };

Код самого модуля должен быть примерно таким:

# Код внутри файла MyModule.pm
package MyModule;
# ...
sub import {
    # $self хранит имя модуля, а массив @args - переданные аргументы.
    my ( $self, @args ) = @_;
    print "import '$self'\n";
    for (@args) {
        print " " . $_ . "\n";
    }
    # ...
}

sub unimport {
    my ( $self, @args ) = @_;
    print "unimport '$self'\n";
    for (@args) {
        print " " . $_ . "\n";
    }
}
# ...

Во время экспорта модуля директивой use, она неявно найдет функцию import() и передаст ей аргументы

import 'MyModule'
 arg1
 arg2
 arg3

Аналогично с директивой no — она будет неявно вызывать функцию unimport():

unimport 'MyModule'
 arg1
 arg2
 arg3

Обычно в качестве аргументов экспорта модуля, программист передает имена символов, которые должны быть загружены в таблицу символов вызывающего кода, но это не обязательно. В некоторых случаях вы можете передавать флаги или инструкции по режиму загрузки/выгрузки модуля. Чтобы аргументы загрузки модуля отличать от загружаемых символов, чаще всего разработчики помечают их символами двоеточия в начале имени, подобно атрибуту (например, :DEFAULT в модуле Exporter).

До появления модуля Exporter разработчики часто писали свою реализацию метода import(). В большинстве случаев она будет похожа на что-то типа такого:

package MyModule;
use strict;

sub import {
    my ( $self, @args ) = @_;
    my $caller = caller();

    # Отключаем ограничение, чтобы иметь возможность разрешать символические ссылки на функции.
    no strict "refs";
    for my $arg (@args) {
        # Экспортируем функции текущего модуля в вызывающий его код путем
        # определения в таблице символов вызывающего кода ссылок на функции текущего модуля.
        # Мы это реализуем через символические ссылки, которые получаются конкатенацией
        # имени пакета модуля с именем символа, переданного модулю. Ожидается, что
        # клиентский код правильно передает имена, иначе такой трюк не пройдет.
        *{"${caller}::${arg}"} = sub {
            return &{__PACKAGE__ ."::$arg"};
        };
    }
}

#...
sub function1 {
    print "Function 1\n";
}

sub function2 {
    print "Function 2\n";
}

1;

Тогда вызывающий код может импортировать функции модуля в своё пространство так:

#!/usr/bin/perl

use strict;

# Обратите внимание, что мы импортировали только одну функцию модуля.
use MyModule qw { function1 };

eval {
    &function1;
    &function2;
};
print $@ if $@;

Вывод будет таким

Function 1
Undefined subroutine &main::function2 called at run.pl line 9.

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

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

Сложный модуль с вложенностями

править

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

Для создания сложной иерархии, Perl заимствует идеи из языков программирования C++ и Java. Из Java берется идея использования структуры файловой системы для создания подобия Java-пакетов для исходных файлов, а у C++ способ создания вложенности у пространств имен. Для создания вложенной структуры у модуля, необходимо следовать следующим правилам:

  1. Размещайте код модуля в каталоге, имя которого должно состоять из букв и цифр, и не должно начинаться на цифру[1]. Имя каталога будет верхним уровнем пространства имен модуля. Это аналогично тому, как классы размещаются в пакетах на языке Java. Соответственно имена модулей лучше начинать с большой буквы.
  2. Если подмодуль размещается в некотором каталоге (вложенном пакете), то в имени его пространства имен необходимо использовать два символа двоеточия :: чтобы выразить вложенность, аналогично тому, как это делается в C++. Например, запись package MyModule::SubModule::Util; означает, что файл Util.pm должен быть расположен по пути MyModule/SubModule/Util.pm.
  3. Чтобы сложный модуль можно было подключить, необходимо, чтобы в служебном массиве @INC был путь к директории, являющейся родительской по отношению к каталогу модуля.

Переопределение

править

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

Основным инструментом в переопределении выступает массив @ISA, который хранит список пространств имен (модулей), которые нужно проверить по порядку в поисках функции, определения которой нет в таблице символов текущего пространства имен. Исполнение этого контролирует сам интерпретатор. Рассмотрим простейший пример переопределения. Этот пример не нужно использовать при написании реальных программ, но он приоткрывает завесу над тем, как примерно работает модуль Exporter.

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

# Примечание: опущены несущественные для изложения директивы.

#-----------------------------------------------------------------------------
## НАЧАЛО ИНТЕРФЕЙСНОГО МОДУЛЯ (представим, что он в отдельном файле)
#-----------------------------------------------------------------------------
package InterfaceModule;

sub sum {
    # Когда функция переопределена в другом пакете или функция вызывается из
    # другого пакета по @ISA, в первом аргументе всегда хранится имя вызывающего
    # пакета, иначе список начинается сразу с передаваемых аргументов. Следующая
    # конструкция учитывает все эти варианты.
    my $caller = ref $_[0] eq __PACKAGE__ 
                    ? ref shift 
                    : scalar @_ > 2 ? ref shift : __PACKAGE__;
    my $arg1 = shift || 0;
    my $arg2 = shift || 0;
    print "$caller: $arg1 + $arg2\n";
    return $arg1 + $arg2;
}

#-----------------------------------------------------------------------------
## НАЧАЛО МОДУЛЯ КОНКРЕТНОЙ РЕАЛИЗАЦИИ (представим, что он в отдельном файле)
#-----------------------------------------------------------------------------
package ConcreteModule_1;

# Подгружаем родительский модуль в @ISA, чтобы иметь возможность переопределять метод.
push @ConcreteModule_1::ISA, 'InterfaceModule';

# Вызов переопределенной реализации.
print $self->sum(2, 2), "\n";

# В переопределенной версии можно передавать больше одного аргумента.
print $self->sum(2, 2, 3, 6), "\n";

# Вызов может быть непосредственно от имени пакета (ссылка конструируется за вас).
print ConcreteModule_1->sum(10, 11, 12, 13), "\n";

# Не играет роли в какой части модуля начинать переопределение, так как связывание происходит динамически.
sub sum {
    my ( $self, @args ) = @_;
    print STDOUT (ref $self) . ": " . (join " + ", @args) . "\n";
    my $result = 0;
    $result +=$_ for @args;
    return $result;
}

BEGIN {
    # Чтобы иметь возможность использовать @ISA, нам нужно получить ссылку на текущий модуль.
    $self = {};
    bless $self, __PACKAGE__;
}

#-----------------------------------------------------------------------------
## НАЧАЛО МОДУЛЯ ДРУГОЙ КОНКРЕТНОЙ РЕАЛИЗАЦИИ (представим, что он в отдельном файле)
#-----------------------------------------------------------------------------
package ConcreteModule_2;

push @ConcreteModule_2::ISA, 'InterfaceModule';

# Интерфейсная реализация может быть вызвана по @ISA ...
print $self->sum(3, 3), "\n";

# ... либо напрямую от имени пакета ...
print ConcreteModule_2->sum(6, 6), "\n";

# ... либо вы можете использовать квалифицированный вызов, но это не так гибко, так как вы привязываетесь к интерфейсу.
print InterfaceModule::sum(4, 4), "\n";

BEGIN {
    # Чтобы иметь возможность использовать @ISA, нам нужно получить ссылку на текущий модуль.
    $self = {};
    bless $self, __PACKAGE__;
}

Для конкретно этого примера мы опустили много директив, которые нужны, когда модули разнесены по файлам. Здесь мы имитируем работу Exporter через конструктор пакета (BEGIN). Единственное, что тут не показано от модуля Exporter это создание т.н. wrapper-функций, которые будут размещаться в таблице символов конкретного пакета и через которые вызовы пробрасываются на родительские (интерфейсные) реализации методов. Эти функции строятся соответственно по массивам @EXPORT и @EXPORT_OK.

Если вызвать эту программу, мы получим такой ответ:

ConcreteModule_1: 2 + 2
4
ConcreteModule_1: 2 + 2 + 3 + 6
13
: 10 + 11 + 12 + 13
46
ConcreteModule_2: 3 + 3        
6
: 6 + 6
12
InterfaceModule: 4 + 4
8

Чтобы механизм переопределения заработал, мы должны построить ссылку на текущий модуль (в главе Объектно-ориентированное программирование в Perl вы узнаете, что в Perl так строятся классы, а демонстрируемый вызов — это вызов статической функции пакета), которая строится в конструкторе пакета через вызов встроенной функции bless. Далее, пользуясь этой ссылкой, мы обращаемся к функциям пакета через разыменовывание ссылки на функцию. После этого весь механизм позднего связывания берет на себя интерпретатор. Так, в реализации второго модуля, этот механизм без проблем нашел переопределение базовой реализации, даже несмотря на то, что её определение находится позже вызова. В третьем модуле механизм позднего связывания не находит реализации функции sum, поэтому он обращается к массиву @ISA и пытается найти функцию в ближайшем (слева на право в списке @ISA) пакете, коей является интерфейсная реализация.

Перегрузка

править

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

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

Некоторые паттерны проектирования перегруженных функций перечислены ниже:

  • Анализ количества входящих аргументов:
    sub overloaded {
        # ...
        if (@_ >= 2) {
            # Передано два и больше аргументов.
        } elsif (@_ == 1) {
            # Передан один аргумент.
        } elsif (!@_) {
            # Ничего не передано.
        }
        # ...
    }
    
  • Анализ типа входящих аргументов:
    sub overloaded {
        # ...
        if (ref $_[0] eq 'ARRAY') {
            # Передана ссылка на массив.
        } elsif (ref $_[0] eq 'HASH') {
            # Передана ссылка на хеш.
        } else {
            # Передано, что-то другое или ничего не передано.
        }
        # ...
    }
    
  • Перегрузка через таблицу перегрузки:
    # Таблица перегрузки.
    our %_handlers = (
        "operation1" => \&_variant1,
        "operation2" => \&_variant2,
    );
    
    sub overloaded {
        my $oper_id = shift;
        $_handlers{$oper_id}->(@_) if exists $_handlers{$oper_id};
    }
    
    sub _variant1 {
        print "Called '_variant1': @_\n";
    }
    
    sub _variant2 {
        print "Called '_variant2': @_\n";
    }
    
    # ...
    
    overloaded "operation1", 1, 2, 3;
    overloaded "operation2", 1, 2, 3, 4, 5;
    

Кроме этого, стандартная библиотека Perl имеет стандартный модуль overload, который позволяет перегружать некоторые операции по отношению к классу, например:

package OverloadedPackage;

# Перегрузка операции заключения в кавычки.
use overload
    q{""} => 'to_string';

sub to_string {
    my $self = shift;
    return "called overloaded implementation";
}

sub new {
    $self = {};
    bless $self;
    return $self;
}

$var = OverloadedPackage->new;

print "Demo: " . "$var", "\n";  # Вызывается подстановочный контекст.
print "Demo: " . $var, "\n";    # Для конкатенации подстановочный контекст также вызывается.

# Результат:
# Demo: called overloaded implementation
# Demo: called overloaded implementation

Но мы забегаем с предыдущим примером вперед, так как это уже относится к перегрузке операторов для классов (как в C++).

Атрибуты

править

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

Если проводить аналогии с другими языками программирования, то атрибуты по смыслу очень похожи на аннотации в языке Java. Если размышлять в контексте современных паттернов проектирования приложений, атрибуты могут использоваться для декорирования методов; с помощью них можно делать проверки допустимости значений перед инициализацией. Также вы можете привнести парадигму аспектно-ориентированного программирования в язык Perl, например, чтобы отслеживать контекст выполнения процедур. Атрибуты могут использоваться в тестировании, например, чтобы помечать тестирующие процедуры. В объектно-ориентированной парадигме, через атрибуты можно проверять правильность использования класса, например, можно имитировать проверку инкапсуляции данных класса и прерывать исполнение программы, если программист, использующий ваш класс, нарушает её в некоторой части программы.

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

my $scalar0 : MyAttribute = 300;
# Пробел после двоеточия необязателен.
my $scalar1 :Attribute = 400;
# Атрибут может нести параметр. С точки зрения интерпретатора, это просто строковый литерал.
my $scalar2 : MyAttribute(parameter) = 300;
# Атрибутов при желании может быть больше одного. Все они отделяются друг от друга двоеточием.
my $scalar3 : MyAttribute : MyAttribute_1(param1=value1, param2=value2) = 100;

Так как в Perl некоторые стандартные модули тоже объявляют атрибуты, чтобы не конфликтовать с ними, рекомендуется пользовательские атрибуты всегда именовать с большой буквы.

Атрибуты на процедуры навешиваются похожим способом:

sub procedure :MyAttribute :MyAttribute_1(param) { 
     return 0; 
}

Недостаточно просто навесить атрибут: пакет, в котором используется хотя бы один атрибут, должен объявить одну или несколько процедур обработки атрибутов (что-то похожее требуется и в Java для обработки аннотаций). На каждый тип данных, на которых есть атрибуты, нужно объявить по одному обработчику, имена которых строятся по такому принципу:

sub MODIFY_<тип-данных>_ATTRIBUTES { ... }

# Например, обработчик для атрибутов всех скаляров будет:
sub MODIFY_SCALAR_ATTRIBUTES { ... }

# А обработчик для всех функций:
sub MODIFY_CODE_ATTRIBUTES { ... }

# и так далее по аналогии. Т.е. нужно использовать идентификатор, возвращаемый ref.

Обработчики всегда вызываются автоматически, единственное, что момент вызова для процедур и для прочих типов данных различный: для процедур обработчик атрибутов вызывается сразу за конструкторами пакета BEGIN, а для других типов данных — в момент инициализации значением.

Обработчик атрибутов обычно имеет такую реализацию:

sub MODIFY_<тип-данных>_ATTRIBUTES { 
    my $package = shift;
    my $source = shift;
    my @attributes = @_;
    #########
    # Обработка атрибутов ...
    while (defined (my $attr = pop @attributes)) {
        # ...
    }
    return @attributes;
}

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

Следующий пример демонстрирует процедуру обработки атрибутов скаляров и функций внутри пакета main. Так как мы будем просто печатать атрибуты, мы напишем универсальную функцию, а обработчики атрибутов мы сгенерируем динамически.

use strict;

# Генерирует обработчики процедур для функций и скаляров. Внутри реализации мы просто вызываем
# функцию '__attribute_processor', которой передаются аргументы обработчика.
BEGIN {
    no strict 'refs';
    use subs qw { __attribute_processor };
    foreach my $datatype ('CODE', 'SCALAR') {
         *{'MODIFY_' . $datatype . '_ATTRIBUTES'} = sub {
            print 'MODIFY_' . $datatype . '_ATTRIBUTES' . "\n";
            __attribute_processor @_;
        };
    }
}

# Процедура просто печатает все прикрепленные атрибуты.
sub __attribute_processor {
    my $package = shift;
    my $source = shift;
    my @attributes = @_;
    print "Package: $package\nData: $source\n";
    my $counter;
    while (defined (my $attr = pop @attributes)) {
        print " Attribute #" . ++$counter . ": $attr\n";
    }
    return @attributes;
}

my $scalar0 : MyAttribute = 300;
my $scalar1 :Attribute = 400;
my $scalar2 : MyAttribute(parameter) = 300;
my $scalar3 : MyAttribute : MyAttribute_1(param1=value1, param2=value2) = 100;

sub procedure :MyAttribute :MyAttribute_1(param) { 
     return 0; 
}
MODIFY_CODE_ATTRIBUTES
Package: main
Data: CODE(0xa00069ba8)
 Attribute #1: MyAttribute_1(param)
 Attribute #2: MyAttribute
MODIFY_SCALAR_ATTRIBUTES
Package: main
Data: SCALAR(0xa0003ab20)
 Attribute #1: MyAttribute
MODIFY_SCALAR_ATTRIBUTES
Package: main
Data: SCALAR(0xa00053018)
 Attribute #1: Attribute
MODIFY_SCALAR_ATTRIBUTES
Package: main
Data: SCALAR(0xa00069dd0)
 Attribute #1: MyAttribute(parameter)
MODIFY_SCALAR_ATTRIBUTES
Package: main
Data: SCALAR(0xa000591d0)
 Attribute #1: MyAttribute_1(param1=value1, param2=value2)
 Attribute #2: MyAttribute

По отладочной печати вы можете видеть, что MODIFY_CODE_ATTRIBUTES вызывается сразу за всеми конструкторами пакета, MODIFY_SCALAR_ATTRIBUTES — в порядке объявления скаляров. Обратите внимание, что во втором аргументе передается ссылка на программный объект, по которой вы можете его видоизменять в зависимости от переданных атрибутов и от их логики.

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

# Процедура, которая вызывается для атрибута 'print_before';
sub __print_before_execution {
    print "*** START PROCEDURE ***\n";
    print "** Args: @_\n";
    print "***********************\n";
}

sub MODIFY_CODE_ATTRIBUTES {
    my $package = shift;
    my $source = shift;
    my @attributes = @_;
    my @allowed_attrs = qw{ print_before };
    my @bad_attrs;
    # Для получения имени процедуры по ссылке.
    use B qw(svref_2object);
    while (defined (my $attr = pop @attributes)) {
        if ( grep( /^$attr$/, @allowed_attrs ) ) {
            my $sub_name = svref_2object($source)->GV->NAME;
            if ($attr eq 'print_before') {
                no strict 'refs';
                # Переопределяем таблицу символов пакета, внедряя декоратор в вызов процедуры с атрибутом.
                *{$package . '::' . $sub_name} = sub {
                    __print_before_execution @_;     # Это декоратор.
                    &$source(@_);                    # Это декорируемая процедура.
                }
            }
        } else {
            push @bad_attrs, $attr;
        }
    } 
    return @bad_attrs;
}

# Декорируем первую процедуру.
sub test_procedure :print_before {
    print "In the 'test_procedure': @_\n";
}

# Декорируем вторую процедуру.
sub another_procedure :print_before {

}

test_procedure 1, 2, 3;
another_procedure "AAA", "BBBB", 3;
*** START PROCEDURE ***
** Args: 1 2 3
***********************
In the 'test_procedure': 1 2 3
*** START PROCEDURE ***
** Args: AAA BBBB 3
***********************

На таком простом примере можно видеть, как много вспомогательного кода нужно написать. На практике проще воспользоваться встроенным модулем Attribute::Handlers, который работает похоже. Если переписать предыдущий пример с использованием библиотечного модуля, реализация будет такой.

use Attribute::Handlers;

sub __print_before_execution {
    print "*** START PROCEDURE ***\n";
    print "** Args: @_\n";
    print "***********************\n";
}

sub print_before : ATTR(CODE) {
    my ($package, $symbol, $referent, $attr, $data, $phase, $filename, $linenumber) = @_;
    my $function_name = *{$symbol}{NAME};
 
    my $new = sub {
        my $params = join ', ', @_;
        __print_before_execution($params);
        my @results = $referent->(@_);
        return @results;
    };
 
    no warnings qw(redefine);
    *$symbol = $new;
}

# Декорируем первую процедуру.
sub test_procedure :print_before {
    print "In the 'test_procedure': @_\n";
}

# Декорируем вторую процедуру.
sub another_procedure :print_before {

}

test_procedure 1, 2, 3;
another_procedure "AAA", "BBBB", 3;

Результат аналогичен предыдущему.

Базовые модули Perl

править

В стандартной поставке Perl включено множество стандартных модулей, некоторые из которых мы перечислим в приложении (см. Некоторые сведения о Perl 5/Приложения#Библиотечные модули Perl). Помимо них, за много лет существования Perl, была создана огромная коллекция модулей CPAN (Comprehensive Perl Archive Network), доступ к которой можно получить через Интернет. Обычно вы должны сначала посмотреть не была ли решена ваша задача в виде некоторого модуля CPAN. Если такой модуль существует, вам следует загрузить его и установить в своей системе. Исходные тексты всех модулей Perl распространяются открыто, поэтому вы всегда сможете познакомиться с тем, как они работают.

Чтобы посмотреть, какие модули установлены в вашей дистрибутивной сборке Perl, можно воспользоваться следующей консольной командой, которая ищет все файлы с расширением .pm по путям в массиве @INC:

$ find $(perl -e 'print "@INC"') -type f -name "*.pm" -print

Прагма-библиотеки

править

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

Как и любые другие модули, директивы подключаются с помощью use() и отключаются с помощью no().

Обычно директива применяется ко всему пакету/библиотеке/модулю, либо директивы применяют к отрезку программы. Например, чтобы ускорить вычисления, директивой integer можно временно запретить вычисления с плавающей точкой, оставив только целочисленные:

print 2 / 3, "\n"; # 0.666666666666667
use integer;
print 2 / 3, "\n"; # 0
no integer;
print 2 / 3, "\n"; # 0.666666666666667

В дистрибутивный комплект Perl входит стандартный набор прагма-библиотек. Некоторые из них мы рассмотрим ниже.

С прагма-библиотеками следует быть осторожными, так как от релиза к релизу они могут менять поведение. Большинство учебников по Perl не успевают за этими изменениями, поэтому сведения из очень старых уже не актуальны для последних релизов языка. Этим мы хотим сказать, что не ленитесь сверяться с документацией к конкретному релизу на perldoc.perl.org.

Директива strict
править

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

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

use strict;

# Аналогично следующим трем вызовам:

# Запрещает объявление переменных без явного указания их области видимости
use strict "vars";

# Запрещает символические ссылки
use strict "refs";

# Ужесточает правила для идентификаторов:
# если идентификатор не заключен в фигурные скобки и не стоит
# слева от =>, то нельзя его записывать без кавычек и без идентификатора типа.
# При этом, если он является именем процедуры, то это ошибкой не считается.
use strict "subs";

Ниже приведены антипримеры того, что запрещает директива.

# Примечание: везде, где написано НЕЛЬЗЯ, оператор запретит директива.

use strict 'refs';

$ref = \3.1415;
print $$ref, "\n";  # МОЖНО: разыменовывается жесткая ссылка.

$ref = "foo";
print $$ref, "\n"; # НЕЛЬЗЯ: попытка разыменовать символическую ссылку.

$file = "STDOUT";
print $file "Hi!"; # НЕЛЬЗЯ: без запятой после $file и без директивы, интерпретатор
                   # попытался бы разрешить символическую ссылку, однако сейчас они
                   # запрещены директивой, поэтому эта строка не скомпилируется.

# ПРАВИЛЬНО
$file = *STDOUT;
print $file "Hi!";

# Есть одно ИСКЛЮЧЕНИЕ из правил.
$bar = \&{'foo'};
&$bar; # МОЖНО: ссылка на функцию разрешится как символическая.
# Примечание: везде, где написано НЕЛЬЗЯ, оператор запретит директива.

use strict 'vars';

$main::var = 1; # МОЖНО: есть квалификатор пакета

$var1 = 2;  # НЕЛЬЗЯ: область видимости не указана явно.

our $var1 = 2;    # МОЖНО: our определяет область видимости пакета.
my $lexical = 3;  # МОЖНО: my определяет лексическую область видимости.
local $var1 = 4;  # МОЖНО: потому что переменная $var1 была к этому моменту определена.

local $var2 = 5;  # НЕЛЬЗЯ: переменная $var2 должна быть определена к этому моменту,
                  # даже несмотря на то, что local определяет видимость.

package MyPackage;

our $var3 = 6;    # МОЖНО: our определяет область видимости пакета.
# Примечание: везде, где написано НЕЛЬЗЯ, оператор запретит директива.

use strict 'subs';

sub func { return 0 }

$scalar = string;    # НЕЛЬЗЯ: нужны кавычки для литерала.
$scalar = "string";  # ПРАВИЛЬНО

func 3, arg2;   # НЕЛЬЗЯ: второй аргумент должен быть закавычен.
func 3, "arg2"; # ПРАВИЛЬНО

$ref = \&func;  # МОЖНО: потому что func это имя функции.

До версии интерпретатора 5.36 эту директиву нужно было прописывать всегда явно. Начиная с версии 5.36 достаточно писать следующим образом:

use v5.36;  # Директива 'strict' включается неявно.
...

Здесь же отметим, что до недавнего времени (а конкретнее до Perl 5.6.0) при включенной use strict 'vars';, глобальные переменные нужно было объявлять через директиву vars, например

use vars qw($frob @mung %seen);

С версии Perl 5.6.0 глобальные переменные пакета следует объявлять исключительно через функцию our.

Директива lib
править

Директива lib позволяет редактировать массив @INC из программы. Напомним, что этот массив используется для определения путей, по которым нужно искать модули и библиотеки.

# Добавит список к тому, что уже есть в массиве.
use lib <список-директорий>;

# Очистит @INC целиком
no lib;

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

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

use strict;  # Добавим директиву, чтобы ужесточить проверки.
use lib qw{ . }; # Добавим текущую рабочую директорию в @INC.
use TestModule qw{ :DEFAULT $scalar };

test "one", "two", "three", "four";
printContent;
print __PACKAGE__ . ": TestModule::Scalar = $scalar\n";
Директива subs
править

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

use subs <список-имен-процедур>;

Пример

example1 1, 2, 3; # НЕЛЬЗЯ: функция еще к этому моменту не определена.

example1 (1, 2, 3); # ПРАВИЛЬНО: в этой точке скобки обязательны.

use subs qw{ example1 example2 example3 };

example1 1, 2, 3; # МОЖНО: функция как бы определена к этому моменту через директиву.

sub example1 { return 0 }
sub example2 { return 0 }
sub example3 { return 0 }
Директива parent
править

Используется, чтобы работать с массивом @ISA не напрямую, а через директиву. Позволяет добавлять модули в массив @ISA, чтобы, как это было описано выше, искать функции в родительских модулях, если их нет в текущем.

Например, запись

package CurrentModule;

use parent qw { ParentModule_1 ParentModule_2 };

аналогична

package CurrentModule;

BEGIN {
   require ParentModule_1;
   require ParentModule_2;
   push @ISA, qw(ParentModule_1 ParentModule_2);
}

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

package ParentModule_1;
# ...
package ParentModule_2;
# ...
package CurrentModule;

use parent -norequire, 'ParentModule_1', 'ParentModule_2';

что аналогично записи

# ...
push @CurrentModule::ISA, 'ParentModule_1', 'ParentModule_2';

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

Данная директива имеет более старую версию base, которая работает похожим образом, но ей не рекомендуется пользоваться, так как она ориентирована на работу совместно с директивой fields, что ухудшает её производительность.

Примечания

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



← Функции и процедуры Объектно-ориентированное программирование в Perl →