Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to watch particular hash key for changing its value?

I have a hash, e.g. $hash->{'foo'}{'bar'}.

I want to call Carp::cluck in any place where value of bar key changed.

How to do that ? Is there any ready module on CPAN that can do that trick ?

like image 906
Paul Serikov Avatar asked Jan 02 '23 07:01

Paul Serikov


2 Answers

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

produces

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

But that's a fatal error, and it doesn't include a trace (unless Carp::Always is used).

I would recommend adding set magic to the scalar.

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;

produces

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

The same can be accomplished with tie, but it would be more expensive. (Tied variables are built upon magic.)

like image 160
ikegami Avatar answered Jan 13 '23 22:01

ikegami


Tie::Trace almost gets you there.

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();

Output:

'new value' at watch.pl line 6

You can make the output produce a full stack trace by importing Carp::Always or by monkey patching the Tie::Trace::_carpit function or with a $SIG{__WARN__} handler like

$SIG{__WARN__} = sub {
    if (caller(0) eq 'Tie::Trace') {
        # warning is from Tie::Trace
        Carp::cluck(@_);
    } else {
        CORE::warn(@_);
    }
};
...
like image 31
mob Avatar answered Jan 13 '23 21:01

mob