Как избежать объявления глобальной переменной при использовании динамического определения объема Perl?

Я пытаюсь написать 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 ответа

Решение

Давайте выясним, как глобальные переменные с 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, ...), Тогда может иметь смысл объединить этот контекст в объект, которым затем можно манипулировать более контролируемым образом. Управление локальными или глобальными переменными не всегда лучший дизайн.

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

Вы использовали 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
Другие вопросы по тегам