Как создать глобально доступные функции в Perl? - PullRequest
1 голос
/ 06 января 2020

Можно ли создать глобальные функции, доступные во всех пространствах имен, например, perl встроенные функции?

Ответы [ 4 ]

4 голосов
/ 06 января 2020

Прежде всего, «функция» - это имя, данное Perl именованным операторам списка, именованным унарным операторам и именованным нулевым операторам. Они видны повсюду, потому что они операторы, как ,, && и +. Подпрограммы не являются операторами.

Во-вторых, вы спрашиваете, как создать глобальную подпрограмму, но все подпрограммы уже глобальны (видимы повсюду) в Perl! Вам просто нужно проверить имя сабвуфера в пакете, если его нет в текущем пакете. Например, Foo::mysub() будет вызывать my_sub из пакета Foo из любого места.

Но, возможно, вы захотите сказать mysub() вместо Foo::mysub() отовсюду, и это очень плохая идея. Это нарушает основные принципы хорошего программирования. Число типов проблем, которые оно может вызвать, слишком велико, чтобы их перечислять.

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

package Foo;
use Exporter qw( import );
our @EXPORT_OK = qw( my_sub );
our %TAGS = ( ALL => \@EXPORT_OK );
sub my_sub { ... }
1;

Затем вы можете использовать

use Foo qw( my_sub );

для загрузки модуля (если он еще не загружен) и создать my_sub в текущем пакете. Это позволяет ему вызывать sub как my_sub() из пакета, в который он был импортирован.

4 голосов
/ 06 января 2020

Нет ничего простого, что позволило бы как-то «зарегистрировать» пользовательские подпрограммы с помощью интерпретатора или чего-то подобного, чтобы вы могли запускать их как встроенные в любой части программы.

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

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

Сказав это, вот основа c demo

use warnings;
use strict;
use feature 'say';
use Data::Dump qw(dd pp);

use TestMod qw(modsub);

sub t_main { say "In t_main(), from ", __PACKAGE__ }

modsub("Calling from main::");

INIT {
    no strict 'refs';
    foreach my $pkg (qw(TestMod)) {
        *{ $pkg . '::' . 'sub_from_main' } = \&t_main;
    }
    dd \%TestMod::;  
}

Копирует ссылку на t_main из текущего пакета (main::) в таблицу символов $pkg под именем sub_from_main, которую затем можно использовать с этим именем в этом пакете.

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

Модуль благодетеля (или жертвы?) TestMod.pm

package TestMod;

use warnings;
use strict;
use feature 'say';

use Exporter qw(import);
our @EXPORT_OK = qw(modsub);

sub modsub {
    say "In module ", __PACKAGE__, ", args: @_";
    say "Call a sub pushed into this namespace: ";
    sub_from_main();
}

1;

Имя добавленного подпрограммы может быть передано в модули по мере их загрузки, а не жестко закодировано, в этом случае вам нужно написать их import sub вместо заимствования Exporter.

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

0 голосов
/ 08 февраля 2020

Извините за мой поздний ответ и спасибо всем за ваши подробные ответы и объяснения.

Хорошо ... Я понял правильный ответ: ЭТО НЕ ВОЗМОЖНО!

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

0 голосов
/ 06 января 2020

Кажется, что ответ отрицательный, но вы можете реализовать большую часть требуемого поведения, используя таблицу символов *main::main:: для определения подпрограммы во всех пространствах имен.

use strict;
use warnings;

use Data::Dump qw(dd);

my $xx = *main::main::;

package A;

sub test {
   printf "A::%s\n", &the_global;
}

package B;

sub the_global
{
   "This is B::the_global";
}

sub test {
   printf "B::%s\n", &the_global;
}

package main;

my $global_sub = sub { "The Global thing" };
for my $NS (keys %$xx) {
   if ($NS =~ /^[A-Z]::$/) {
      my $x = $NS . 'the_global';
      if (defined &$x) {
          printf "Skipping &%s\n", $x;
      } else {
          printf "Adding &%s\n", $x;
          no strict 'refs';
          *$x = $global_sub;
      }
   }
}

A::test;

Это не будет работать с пакетами, на которые вообще нет ссылок перед запуском для l oop выше. Но это произойдет только в том случае, если require, use или package было eval 'd после запуска кода.

Это также все еще проблема компилятора! Вам нужно либо ссылаться на глобальную функцию как the_global() или &the_global, если вы (как и должно быть) используете use strict.

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