9.5 Example: Monitoring Variables
tie
makes it really convenient to monitor a variable. In this section, we will develop a module called
Monitor.pm
that prints out a message on
use Monitor; monitor(\$x, 'x'); monitor(\%y, 'y'); Whenever $x or %y is changed, this module prints out something like this on STDERR : Wrote : $x ... 10 Read : $x ... 10 Died : $x Wrote : $y{a} ... 1 Cleared : %y This module is useful while debugging, where it is not clear at what point a certain variable is changing, especially when it changes indirectly through a reference. This module can be enhanced to support watch expressions such as print 'ahhh' when $array[5] > 10 . Given Perl's support for eval , this is a reasonably simple task.
Monitor, shown in Example 9.3 , delegates responsibility to a nested module dedicated to each type of value (scalar, array, hash). The tie constructors in these modules return a blessed anonymous array (the tied object), which stores the name supplied by the user (the second parameter of monitor ) and the current value of the variable. Example 9.3: Monitor.pm#---------------------------------------------------------------------- package Monitor ; require Exporter; @ISA = ("Exporter"); @EXPORT = qw(monitor unmonitor); use strict; sub monitor { my ($r_var, $name) = @_; my ($type) = ref($r_var); if ($type =~ /SCALAR/) { return tie $$r_var, 'Monitor::Scalar', $r_var, $name; } elsif ($type =~ /ARRAY/) { return tie @$r_var, 'Monitor::Array', $r_var, $name; } elsif ($type =~ /HASH/) { return tie %$r_var, 'Monitor::Hash', $r_var, $name; } else { print STDERR "require ref. to scalar, array or hash" unless $type; } } sub unmonitor { my ($r_var) = @_; my ($type) = ref($r_var); my $obj; if ($type =~ /SCALAR/) { Monitor::Scalar->unmonitor($r_var); } elsif ($type =~ /ARRAY/) { Monitor::Array->unmonitor($r_var); } elsif ($type =~ /HASH/) { Monitor::Hash->unmonitor($r_var); } else { print STDERR "require ref. to scalar, array or hash" unless $type; } } #------------------------------------------------------------------------ package Monitor::Scalar ; sub TIESCALAR { my ($pkg, $rval, $name) = @_; my $obj = [$name, $$rval]; bless $obj, $pkg; return $obj; } sub FETCH { my ($obj) = @_; my $val = $obj->[1]; print STDERR 'Read $', $obj->[0], " ... $val \n"; return $val; } sub STORE { my ($obj, $val) = @_; print STDERR 'Wrote $', $obj->[0], " ... $val \n"; $obj->[1] = $val; return $val; } sub unmonitor { my ($pkg, $r_var) = @_; my $val; { my $obj = tied $$r_var; $val = $obj->[1]; $obj->[0] = "_UNMONITORED_"; } untie $$r_var; $$r_var = $val; } sub DESTROY { my ($obj) = @_; if ($obj->[0] ne '_UNMONITORED_') { print STDERR 'Died $', $obj->[0]; } } #------------------------------------------------------------------------ package Monitor::Array ; sub TIEARRAY { my ($pkg, $rarray, $name) = @_; my $obj = [$name, [@$rarray]]; bless $obj, $pkg; return $obj; } sub FETCH { my ($obj, $index) = @_; my $val = $obj->[1]->[$index]; print STDERR 'Read $', $obj->[0], "[$index] ... $val\n"; return $val; } sub STORE { my ($obj, $index, $val) = @_; print STDERR 'Wrote $', $obj->[0], "[$index] ... $val\n"; $obj->[1]->[$index] = $val; return $val; } sub DESTROY { my ($obj) = @_; if ($obj->[0] ne '_UNMONITORED_') { print STDERR 'Died %', $obj->[0]; } } sub unmonitor { my ($pkg, $r_var) = @_; my $r_array; { my $obj = tied @$r_var; $r_array = $obj->[1]; $obj->[0] = "_UNMONITORED_"; } untie @$r_var; @$r_var = @$r_array; } #------------------------------------------------------------------------ package Monitor::Hash ; sub TIEHASH { my ($pkg, $rhash, $name) = @_; my $obj = [$name, {%$rhash}]; return (bless $obj, $pkg); } sub CLEAR { my ($obj) = @_; print STDERR 'Cleared %', $obj->[0], "\n"; } sub FETCH { my ($obj, $index) = @_; my $val = $obj->[1]->{$index}; print STDERR 'Read $', $obj->[0], "{$index} ... $val\n"; return $val; } sub STORE { my ($obj, $index, $val) = @_; print STDERR 'Wrote $', $obj->[0], "{$index} ... $val\n"; $obj->[1]->{$index} = $val; return $val; } sub DESTROY { my ($obj) = @_; if ($obj->[0] ne '_UNMONITORED_') { print STDERR 'Died %', $obj->[0]; } } sub unmonitor { my ($pkg, $r_var) = @_; my $r_hash; { my $obj = tied %$r_var; $r_hash = $obj->[1]; $obj->[0] = "_UNMONITORED_"; } untie %$r_var; %$r_var = %$r_hash; } 1; unmonitor is slightly tricky. We want to do an untie , but Perl restores the variable's value to that held by it just before tie was invoked. Clearly, this is undesirable. We want this operation to go on without the variable's user being affected in any way. Since we have the variable's current value as an attribute of the tied object, we can attempt to restore the value after the untie. Unfortunately, the following code doesn't quite work: # For a tied scalar my $obj = tied $$r_var; # Get the object tied to the variable $latest_value = $obj->[1]; # Extract the latest value untie $$r_var; # untie $$r_var = $latest_value; # Restore the variable to the latest # value Perl complains, "Can't untie: 1 inner references still exist ..." if the -w flag is turned on. The problem is that the local variable $obj bumps up the reference count of the tied object, so an untie is not able to DESTROY the tied object. The solution is fairly straightforward: extract the value in an inner block and let $obj go out of scope, like this: my $latest_value; { my $obj = tied $$r_var; $latest_value = $obj->[1]; # Extract the latest value. # Note that $latest_value is defined # outside this inner block } # $obj is no longer in scope, so we can peacefully untie. untie $$r_var; $$r_var = $latest_value; |
|