Как посмотреть конкретный хэш-ключ для изменения его значения?

У меня есть хеш, например. $hash->{'foo'}{'bar'}.

Я хочу вызвать Carp::cluck в любом месте, где изменилось значение ключа bar.

Как это сделать ? Есть ли готовый модуль на CPAN, который может сделать этот трюк?


person Paul Serikov    schedule 13.02.2019    source источник
comment
Является ли ваш хэш только двумерным или может отображаться более/менее двух измерений?   -  person sergiotarxz    schedule 13.02.2019
comment
Используйте связанный хеш и перегрузите нужные методы: perldoc.perl.org/Tie/Hash.html   -  person Miguel Prz    schedule 13.02.2019


Ответы (3)


my $hash = { foo => { bar => 1 } };
Internals::SvREADONLY( $hash->{foo}{bar}, 1 );
$hash->{foo}{bar} = 2;

производит

Modification of a read-only value attempted at -e line 4.

Но это фатальная ошибка, и она не включает трассировку (если только не используется Carp::Always).

Я бы рекомендовал добавить в скаляр set magic.

use Carp            qw( cluck );
use Variable::Magic qw( wizard cast );

my $wizard = wizard(
   set => sub {
      cluck("Warning: Modification of a read-only value attempted");
   },
);

my $hash = { foo => { bar => 1 } };
cast( $hash->{foo}{bar}, $wizard );
$hash->{foo}{bar} = 2;

производит

Warning: Modification of a read-only value attempted at -e line 6.
        main::__ANON__(SCALAR(0x4200c90), undef) called at -e line 12
        eval {...} called at -e line 12

То же самое можно сделать и с tie, но это будет дороже. (Связанные переменные построены на основе magic.)

person ikegami    schedule 13.02.2019
comment
Другая причина, по которой set magic может быть предпочтительнее в этом случае, заключается в том, что ограниченные хэши (то, что вы получаете, когда устанавливаете хэш только для чтения) немного больше, чем просто только для чтения; это также ошибка, если вы обращаетесь к несуществующему ключу, даже как rvalue. (см., например, Const::Fast предупреждения) - person Grinnz; 13.02.2019

Tie::Trace почти доводит вас до цели.

use Tie::Trace 'watch';
my $hash = { foo => { bar => "original value" } };
watch $hash->{foo}{bar};
sub f1 { f2() }
sub f2 { f3() }
sub f3 { $hash->{foo}{bar} = "new value" }
f1();

Выход:

'new value' at watch.pl line 6

Вы можете заставить выходные данные создавать полную трассировку стека, импортировав Carp::Always или исправив обезьяну Tie::Trace::_carpit или с обработчиком $SIG{__WARN__}, например

$SIG{__WARN__} = sub {
    if (caller(0) eq 'Tie::Trace') {
        # warning is from Tie::Trace
        Carp::cluck(@_);
    } else {
        CORE::warn(@_);
    }
};
...
person mob    schedule 13.02.2019

Для этого я сделал «простую» функцию, она не работает с хэшами, содержащими массивы:

use v5.28;
use Storable qw(dclone);

my $ori = {
    'hola' => {
        'hola' => 'adios',
        'adios' => 'hola'
    },
    'hey' => 'you'
};
my $copy = dclone($ori);
$ori->{'hola'}{'adios'} = {'good', 'bye', 'hello', 'year'};
compare($ori, $copy, sub { say $_[0]; });

sub compare {
    my $original = shift;
    my $copy = shift;
    my $func = shift;
    for my $el (keys %{$original}) {
        if (ref $original->{$el} eq ref {}) {
                compare($original->{$el}, ref $copy->{$el} 
                        eq ref {} ? $copy->{$el}:{} , $func);
        } else {
            unless ($copy->{$el} eq $original->{$el}) {
                &$func($original->{$el}, {}, $func);
            }
        }
    }
}
~             
person sergiotarxz    schedule 14.02.2019