LanX has asked for the wisdom of the Perl Monks concerning the following question:

Let's say I want to use Tie::Hash to have a temporary wrapper around an existent hash until I untie.

In the following example I'm simply returning any value enclosed in < > .

I'm facing some "complications", which make me doubt I'm doing the right thing.

I'm sure I could kind of avoid all this by copying all data of the original hash when constructing the tie inside TIEHASH, but this seems like a waste of resources.

Any more elegant approach???

use v5.12; use warnings; package main; my %hash; @hash{"a".."c"} = 40 ..42; # init tie # bind wrapper %hash, 'Data::Proxy::TieHash', \%hash; # redundant, why? say $hash{a}; say @hash{"a".."c"}; delete $hash{b}; say @hash{"a".."c"}; untie %hash; # unbind wrapper say @hash{"a".."c"}; BEGIN { package Data::Proxy::TieHash; require Tie::Hash; use Scalar::Util qw/blessed/; use Carp; our @ISA = qw(Tie::ExtraHash); # All methods provided by default, define # only those needing overrides # Accessors access the storage in %{$_[0][0]}; # TIEHASH should return an array reference with the first element # being the reference to the actual storage sub _report { # uncomment to trace #carp "Doing \U$_[0]\E of $_[1] at $_[2].\n" }; sub DELETE { my ($obj, $key) = @_; my ($meta, $orig) =@$obj; _report('DELETE', $orig, $key); my $class = blessed $obj; undef $obj; untie %{$orig}; my $ret = delete $orig->{$key}; tie %{$orig}, $class, $orig; return $ret; } sub FETCH { goto &FETCH1; # use implementation } sub FETCH0 { _report('FETCH', $_[0][1], $_[1]); untie %{$_[0][1]}; my $ret = $_[0][1]->{$_[1]}; tie %{$_[0][1]}, 'Data::Proxy::TieHash', $_[0][1]; return "<$ret>" if defined $ret; return undef; } sub FETCH1 { my ($obj, $key) = @_; my ($meta, $orig) =@$obj; _report('FETCH', $orig, $key); my $class = blessed $obj; undef $obj; # avoid warning untie %{$orig}; my $ret = $orig->{$key}; tie %{$orig}, $class, $orig; return "<$ret>" if defined $ret; return undef; } }
<40> <40><41><42> Use of uninitialized value in say at proxy_tiehash.pl line 21. <40><42> Use of uninitialized value in say at proxy_tiehash.pl line 25. 4042

Cheers Rolf
(addicted to the Perl Programming Language :)
see Wikisyntax for the Monastery

UPDATES

°) this smells like internally causing a recursive data structure

²) OK I got it https://perldoc.perl.org/perltie#The-untie-Gotcha, untie is normally supposed to trigger DESTROY, but inf there are still other refs to the underlying $object DESTROY can't be triggered. In my case it's actually better not to trigger DESTROY and better to silence the warning altogether.

Replies are listed 'Best First'.
Re: using TIEHASH as wrapper/proxy, while avoiding recursion and warnings
by Corion (Patriarch) on Apr 21, 2024 at 18:16 UTC

    Do you really have to wrap / rebless an existing object or can you just create a fresh object that is tied/overloaded/whatever that uses AUTOLOAD to redirect the accesses and method calls to the "original" object?

    Otherwise, I see very little beyond reblessing/re-tieing.

      In my use-case I could actually replace the variable's ref in the functions pad with a new one.

      But in general tie %var is supposed to dock to an existing variable without changing it's name or reference!

      That's why the pedant in me is surprised that I have to jump thru all those hoops.

      And yes I tried to not reinvent the wheel and found Tie::Watch , what they do is actually a flat copy of the whole data-structure in the constructor.

        -shadow (default 1) is 0 to disable array and hash shadowing. To prevent infinite recursion Tie::Watch maintains parallel variables for arrays and hashes. When the watchpoint is created the parallel shadow variable is initialized with the watched variable's contents, and when the watchpoint is deleted the shadow variable is copied to the original variable. Thus, changes made during the watch process are not lost. Shadowing is on my default. If you disable shadowing any changes made to an array or hash are lost when the watchpoint is deleted.

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      see Wikisyntax for the Monastery

Re: using TIEHASH as wrapper/proxy, while avoiding recursion and warnings
by haukex (Archbishop) on Apr 22, 2024 at 10:01 UTC
      The wrapper in Tie::Subset::Hash is a new variable.

      I wanted to be able to tie the original variable while changing the original data.

      This creates a lot of headaches to even access that data, to avoid recursion and related warnings.

      > It's unclear to me what your proxy class is actually supposed to be doing?

      Consider debugging, setting watchpoints, tracing, meta programming,...

      It's not always possible to replace the monitored variable with a proxy, you just want to attach the magic directly.

      There is code in the OP.

      Cheers Rolf
      (addicted to the Perl Programming Language :)
      see Wikisyntax for the Monastery

        I wanted to be able to tie the original variable while changing the original data.

        I see. I remember that when I tried something similar to that with tied filehandles, I got Perl to segfault, so I'm not sure if tie is meant to be used that way.

        It's not always possible to replace the monitored variable with a proxy, you just want to attach the magic directly.

        That's probably true, but I imagine it wouldn't be too difficult to refactor the code in question from using a hash to a hashref?

        Otherwise, if this is just for debugging, you could use refaliasing to replace the target hash with the tied hash? (though to unite you'd probably have to keep a reference to the original hash around)

        use warnings; use strict; use feature 'refaliasing'; use Data::Dump; use Tie::Subset::Hash; my %hash; @hash{"a".."e"} = 41..45; dd \%hash; tie my %hash2, 'Tie::Subset::Hash', \%hash, ['b']; \%hash = \%hash2; dd \%hash; __END__ Aliasing via reference is experimental at foo.pl line 13. { a => 41, b => 42, c => 43, d => 44, e => 45 } { # tied Tie::Subset::Hash b => 42, }