Сохранение пакетов Perl через local

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

Проблема в том, что хеши пакетов local не сохраняет.

Под хешами пакетов я понимаю хеш с двоеточием на конце (%Пакет::) в котором хранятся символы пакета (GLOB).

package A {
  sub fn {}
  $x = 10;
  @x = qw(1 2);
}

use DDP;
p %A::  # -> {
        #    fn   *A::fn  (layers: ),
        #    x    *A::x  (layers: )
        # }

Тут @x и $x находятся в одном глобе - *A::x.

Доступ к ним можно получить так:

package A {
	sub fn {}
	$x = 10;
	@x = qw(1 2);
}

$\ = "\n"; $, = ", ";

print $A::x, ${ *A::x{SCALAR} }, ${ *{ $A::{x} }{SCALAR} }; 
# -> 10, 10, 10
print @A::x, @{ *A::x{ARRAY} }, @{ *{ $A::{x} }{ARRAY} }; 
# -> 1, 2, 1, 2, 1, 2

То есть вначале получаем GLOB из хеша пакета ($A::{x}), разыменовываем его (*{ $A::{x} }), получаем ссылку на скаляр (*{ $A::{x} }{SCALAR}) и, наконец, разыменовываем скаляр (${ *{ $A::{x} }{SCALAR} }).

GLOB же это такой специальный хеш с предустановленными ключами: SCALAR, ARRAY, HASH, CODE, REF, GLOB, LVALUE, FORMAT, IO, VSTRING.

Каждый из них соответствует отдельному типу значения который может хранится в символе (fn и x - символы в пакете A).

Но вернёмся к local.

use DDP;
our %x = qw/a b d c/;
{
	local %x;
	
	$x{a} = "R";
	p %x;  # -> {
         #    a   "R"
         # }
}
p %x;  # -> {
       #    a   "b"
       #    d   "c"
       # }

Как видим local подменила our %x до конца блока.

С my %x local отказывается работать (выбрасывает исключение):

@@файл x.pl:
my %x = qw/a b/;
{
	local %x;
  p %x;
}

@@консоль:
$ perl x.pl
Can't localize lexical variable %x at x.pl line 13.

А теперь самое интересное: на хеш пакета local не ругается, но и не сохраняет его:

package A {
	sub fn { 10 }
	$x = 20;
}

{
	local %A::;
	
	*A::fn = sub {6};
	$A::x = 3;
	
	print &A::fn;  # -> 6
	print $A::x;	 # -> 3
}

print &A::fn;  # -> 6
print $A::x;   # -> 3

То есть $A::x и A::fn не вернулись к первоначальному состоянию.

Тем не менее если сохранять не хеш %A::, а срез всех его ключей (@A::{keys %A::}), то local сработает:

package A {
	sub fn { 10 }
	$x = 20;
}

{
	local @A::{keys %A::};
	
	*A::fn = sub {6};
	$A::x = 3;
	
	print &A::fn;  # -> 6
	print $A::x;	 # -> 3
}

print &A::fn;    # 10
print $A::x;     # 20

Вот и ещё одна тайна perl-а раскрыта!

Хотя не до конца. Если у кого-то есть объяснение такому странному поведению local c хешем пакета, то хотелось бы его увидеть в комментариях.

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

Обратите внимание на наш пакет A, который на самом деле main::A.

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

package A {
	sub fn { 10 }
	$x = 20;
}

package A::A {
	$x = 30;
}

# -> 
#  %main::
#    A::   *main::A
#      fn    *A::fn
#      x     *A::x
#      A::   *A::A::
#        x     *A::A::x

print \%main::A == \%A? "одно и то же": "разное";
# -> одно и то же
print $main::{A::}{A::} == $A::A::? "одно и то же": "разное";
# -> одно и то же

Вот и всё. Остаётся только пожелать нам всем отличного программирования!