The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

NAME/НАИМЕНОВАНИЕ

perlpragma - Как писать пользовательские прагмы

ОПИСАНИЕ

Прагма - это модуль, который влияет на некоторые моменты во время компиляции и во время выполнения Perl-кода. Пример прагмы: strictили warnings. С выходом Perl 5.10 вы не ограничены в разработке прагм, теперь можете создавать пользовательские прагмы, которые будут менять поведение пользовательских функций в лексическом контексте.

Основной пример

Например, вам нужно создать класс, который реализует перегрузку математических операторов, и вы хотели бы использовать свою прагму с функционалом похожим на use integer;. Пример кода:

    use MyMaths;

    my $l = MyMaths->new(1.2);
    my $r = MyMaths->new(3.4);

    print "A: ", $l + $r, "\n";

    use myint;
    print "B: ", $l + $r, "\n";

    {
        no myint;
        print "C: ", $l + $r, "\n";
    }

    print "D: ", $l + $r, "\n";

    no myint;
    print "E: ", $l + $r, "\n";

Результат выполнения кода:

    A: 4.6
    B: 4
    C: 4.6
    D: 4
    E: 4.6

В примере, в котором используется use myint;, оператор сложения работает с целыми числами, значения по умолчанию не определены. Поведение по умолчанию будет восстановлено no myint;

Минимальная реализация пакета MyMaths будет примерно такой:

    package MyMaths;
    use warnings;
    use strict;
    use myint();
    use overload '+' => sub {
        my ($l, $r) = @_;
        # передать 1, чтобы отметить один вызов уровня отсюда
        if (myint::in_effect(1)) {
            int($$l) + int($$r);
        } else {
            $$l + $$r;
        }
    };

    sub new {
        my ($class, $value) = @_;
        bless \$value, $class;
    }

    1;

Примечание. При загрузке пользовательской прагмы myint без параметров С<()> функция import не будет вызвана.

Взаимодействие с Perl во время компиляции внутри пакета myint:

    package myint;

    use strict;
    use warnings;

    sub import {
        $^H{"myint/in_effect"} = 1;
    }

    sub unimport {
        $^H{"myint/in_effect"} = 0;
    }

    sub in_effect {
        my $level = shift // 0;
        my $hinthash = (caller($level))[10];
        return $hinthash->{"myint/in_effect"};
    }

    1;

Прагма реализована как модуль, поэтому use myint; означает:

    BEGIN {
        require myint;
        myint->import();
    }

and no myint; is

    BEGIN {
        require myint;
        myint->unimport();
    }

Следовательно, import и unimport вызываются во время компиляции пользовательского кода.

Пользовательские прагмы сохраняют свое состояние в магическом хеше %^H, следовательно эти две подпрограммы управляют им. Информация о состоянии в %^H сохраняется в op-дереве, и может быть получено во время выполнения с помощью caller(), под индексом 10 в возвращённом списке. В прагме из примера восстановление инкапсулировано в подпрограмме in_effect(), которая в качестве аргументов принимает единственный параметр - число вызовов, оставшихся для нахождения значения прагмы в пользовательском скрипте. Здесь используется caller(), чтобы определить значение $^H{"myint/in_effect"}, когда каждая строка пользовательского скрипта была вызвана. Поэтому в подпрограмме, реализующей перегрузку оператора сложения, используется корректная семантика.

Ключевые наименования

Существует только один %^H, но сколь угодно много модулей, которые хотят использовать его обзорного семантики. Чтобы не наступать друг другу на пальцы, они должны быть уверены, чтобы использовать различные ключи в хэше. Поэтому для обычного модуля для использования только ключи, которые начинаются с имени модуля (имя его основной пакет) и символ "/". После этого модуль идентифицирующей префикс, остальная часть ключа полностью зависит от модуля: он может включать любые символы. Например, модуль Foo::Bar должны использовать ключи, такие как Foo::Bar/baz и Foo::Bar/$%/_!. Модули следующие этому соглашению все дружат друг с другом.

Ядро Perl использует несколько ключей в %^H, которые не следуют этому соглашению, потому что они предшествуют его. Ключи, которые следуют Конвенция не вступит в противоречие с историческими ключей ядра.

Детали реализации

op-дерево является общим для всех потоков. Это означает, что существует возможность, при которой op-дерево "переживет" поток (и следовательно экземпляр интерпретатора), породивший его.Таким образом, настоящие Perl-скаляры не могут храниться в op-дереве. Вместо этого используется компактная форма, которая может хранить только целые значения (со знаком или без), строки или undef; ссылки и числа с плавающей точкой преобразуются в строку. Если вам нужно хранить составные значения или сложные структуры, вам следует сериализовать их , например с помощью pack. Ключи хеша из %^H можно удалять и, как всегда, с помощью exists можно понять, является ли это значение определённым или undef.

Не пытайтесь хранить указатели на структуры данных как целые числа, которые получены из caller и преобразованы обратно, т.к. это будет не безопасным для потоков. Доступ к структуре будет неблокирующим (что не безопасно для Perl скаляров). Такие структуры могут давать утечки памяти, либо быть освобождены, когда породивший их поток завершится. Это может произойти до того, как op-дерево удалит ссылки на них, если его поток переживёт их.

ПЕРЕВОДЧИКИ

  • Николай Мишин <mishin@cpan.org>

  • Анатолий Шарифулин <sharifulin@gmail.com>

  • Алексей Суриков (Language) <KSURi>

  • Михаил Любимов (Language) <mikhail.lyubimov>

  • Дмитрий Константинов (Language) <Dim_K>

  • Евгений Баранов (Language) <Baranov>