Как избежать объявления глобальной переменной при использовании динамического определения объема Perl? - PullRequest
0 голосов
/ 06 июля 2018

Я пытаюсь написать Perl-скрипт, который вызывает функцию, написанную где-то еще (кем-то еще), которая манипулирует некоторыми переменными в области видимости моего скрипта. Допустим, сценарий main.pl и функция есть в funcs.pm. Мой main.pl выглядит так:

use warnings;
use strict;

package plshelp;
use funcs;

my $var = 3;
print "$var\n";   # <--- prints 3

{                 # New scope somehow prevents visibility of $pointer outside
    local our $pointer = \$var;
    change();
}

print "$var\n";   # <--- Ideally should print whatever funcs.pm wanted

По некоторым причинам использование local our $pointer; предотвращает видимость $pointer за пределами области видимости. Но если я просто использую our $pointer;, переменную можно увидеть вне области видимости в main.pl, используя $plshelp::pointer (но не в funcs.pm, так что в любом случае она будет бесполезна). Как примечание, может кто-нибудь объяснить это?

funcs.pm выглядит примерно так:

use warnings;
use strict;

package plshelp;

sub change
{
    ${$pointer} = 4;
}

Я ожидал, что это изменит значение $var и напечатает 4 при запуске основного скрипта. Но я получаю сообщение об ошибке, сообщающее, что $pointer не объявлено Эту ошибку можно устранить, добавив our $pointer; вверху change в funcs.pm, но это создаст ненужную глобальную переменную, которая видна везде. Мы также можем устранить эту ошибку, удалив use strict;, но это кажется плохой идеей. Мы также можем заставить его работать, используя $plshelp::pointer в funcs.pm, но человек, пишущий funcs.pm, не хочет этого делать.

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

Скажем так, невозможно передать аргументы функции по какой-то причине.

Обновление

Кажется, что local our не делает ничего особенного в том, что касается предотвращения видимости. От perldoc :

Это означает, что когда действует use strict 'vars', our позволяет вам использовать переменную пакета, не квалифицируя ее с именем пакета, но только в пределах лексической области нашего объявления. Это применяется немедленно - даже в пределах одного утверждения.

и

Это работает, даже если переменная пакета ранее не использовалась, так как переменные пакета возникают при первом использовании.

Таким образом, это означает, что $pointer «существует» даже после того, как мы покинем фигурные скобки. Просто мы должны ссылаться на него, используя $plshelp::pointer вместо $pointer. Но поскольку мы использовали local до инициализации $pointer, он все еще не определен вне области действия (хотя он все еще "объявлен", что бы это ни значило). Более ясный способ написать это будет (local (our $pointer)) = \$var;. Здесь our $pointer "объявляет" $pointer и возвращает также $pointer. Теперь мы применяем local к этому возвращаемому значению, и эта операция снова возвращает $pointer, который мы присваиваем \$var.

Но это все еще оставляет основной вопрос о том, существует ли хороший способ достижения требуемой функциональности, без ответа.

Ответы [ 2 ]

0 голосов
/ 07 июля 2018

Давайте разберемся, как работают глобальные переменные с our и почему они должны быть объявлены: есть разница между хранением глобальной переменной и видимостью ее неквалифицированного имени.В use strict неопределенные имена переменных не будут косвенно ссылаться на глобальную переменную.

  • Мы всегда можем получить доступ к глобальной переменной с ее полностью определенным именем, например, $Foo::bar.

  • Если глобальная переменная в текущем пакете уже существует во время компиляции и помечена как импортированная переменная, мы можем получить к ней доступ без указания имени, например, $bar.Если пакет Foo написан соответствующим образом, мы можем сказать use Foo qw($bar); say $bar, где $bar теперь является глобальной переменной в нашем пакете.

  • С помощью our $foo мы создаем глобальную переменную.переменная в текущем пакете, если эта переменная еще не существует.Имя переменной также доступно в текущей лексической области, как и переменная объявления my.

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

Тщательно продумывая, когда код компилируется, становится понятно, почему ваш пример в настоящее время не работает:

  • В вашем основном скрипте вы загружаете модуль funcs.Оператор use выполняется в фазе BEGIN, т.е. во время синтаксического анализа.

    use warnings;
    use strict;
    
    package plshelp;
    use funcs;
    
  • Модуль funcs скомпилирован:

    use warnings;
    use strict;
    
    package plshelp;
    
    sub change
    {
        ${$pointer} = 4;
    }
    

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

Канонический способ исправить эту ошибку - объявить имя переменной our $pointer в области видимости.из sub change:

sub change {
    our $pointer;
    ${$pointer} = 4;
}

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


Просто потому, чтовы можете использовать глобальные переменные не означает, что вы должны.У них есть две проблемы:

  • На уровне разработки глобальные переменные не объявляют понятный интерфейс.Используя полное имя, вы можете просто получить доступ к переменной без каких-либо проверок.Они не обеспечивают инкапсуляцию.Это делает хрупкое программное обеспечение и странное действие на расстоянии.

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

Кроме того, глобальные переменные являются глобальными переменными: они могут иметь только одно значение за раз!Определение значения с помощью local может помочь избежать этого в некоторых случаях, но все же могут возникнуть конфликты в сложных системах, где два модуля хотят установить одну и ту же глобальную переменную на разные значения, и эти модули вызывают друг друга.

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

sub change {
  my ($pointer) = @_;
  ${$pointer} = 4;
}

...
my $var = 3;
change(\$var);

Если имеется много контекста, может быть затруднительно передать все эти ссылки: change(\$foo, \$bar, \$baz, \@something_else, \%even_more, ...).Тогда может иметь смысл объединить этот контекст в объект, которым затем можно манипулировать более контролируемым образом.Манипулирование локальными или глобальными переменными - не всегда лучший дизайн.

0 голосов
/ 06 июля 2018

Слишком много ошибок в вашем коде, чтобы просто исправить его

Вы использовали package plshelp как в основном скрипте, так и в модуле, хотя главная точка входа находится в main.pl, а ваш модуль в funcs.pm. Это просто безответственно. Вы представляли, что заявление package предназначено исключительно для рекламы помощи, и не имеет значения, что вы там написали?

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

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

Functions.pm

package Functions;

use strict;
use warnings;

use Exporter 'import';

our @EXPORT_OK = 'change';

sub change {

    my ($ref) = @_;

    $$ref = 4;
}

main.pl

use strict;
use warnings 'all';

use Functions 'change';

my $var = 44;

print "$var\n";
change(\$var);
print "$var\n";

выход

44
4
...