Automatically call hash values that are subroutine references

前端 未结 7 2129
孤街浪徒
孤街浪徒 2021-01-06 07:02

I have a hash with a few values that are not scalar data but rather anonymous subroutines that return scalar data. I want to make this completely transparent to the part of

7条回答
  •  陌清茗
    陌清茗 (楼主)
    2021-01-06 07:31

    I don't believe that the words that others have written in disapproval of the tie mechanism are warranted. None of the authors seem to properly understand how it works and what core library backup is available

    Here's a tie example based on Tie::StdHash

    If you tie a hash to the Tie::StdHash class then it works exactly as a normal hash. That means there's nothing left to write except for methods that you may want to override

    In this case I've overridden TIEHASH so that I could specify the initialisation list in the same statement as the tie command, and FETCH, which calls the superclass's FETCH and then makes a call to it if it happens to be a subroutine reference

    Your tied hash will work as normal except for the change that you have asked for. I hope it is obvious that there is no longer a direct way to retrieve a subroutine reference if you have stored it as a hash value. Such a value will always be replaced by the result of calling it without any parameters

    SpecialHash.pm

    package SpecialHash;
    
    use Tie::Hash;
    use base 'Tie::StdHash';
    
    sub TIEHASH {
        my $class = shift;
        bless { @_ }, $class;
    }
    
    sub FETCH {
        my $self = shift;
        my $val = $self->SUPER::FETCH(@_);
        ref $val eq 'CODE' ? $val->() : $val;
    }
    
    1;
    

    main.pl

    use strict;
    use warnings 'all';
    
    use SpecialHash;
    
    tie my %hash, SpecialHash => (
        key1 => "value1",
        key2 => sub {
            return "value2"; # In the real code, this value can differ
        },
    );
    
    print "$hash{$_}\n" for sort keys %hash;
    

    output

    value1
    value2
    



    Update

    It sounds like your real situation is with an existing hash that looks something like this

    my %hash = (
        a => {
            key_a1 => 'value_a1',
            key_a2 => sub { 'value_a2' },
        },
        b => {
            key_b1 => sub { 'value_b1' },
            key_b2 => 'value_b2',
        },
    );
    

    Using tie on already-populated variables isn't so neat as tying then at the point of declaration and then inserting the values as the data must be copied to the tied object. However the way I have written the TIEHASH method in the SpecialHash class makes this simple to do in the tie statement

    If possible, it would be much better to tie each hash before you put data into it and add it to the primary hash

    This program ties every value of %hash that happens to be a hash reference. The core of this is the statement

    tie %$val, SpecialHash => ( %$val )
    

    which functions identically to

    tie my %hash, SpecialHash => ( ... )
    

    in the previous code but dereferences $val to make the syntax valid, and also uses the current contents of the hash as the initialisation data for the tied hash. That is how the data gets copied

    After that there is just a couple of nested loops that dump the whole of %hash to verify that the ties are working

    use strict;
    use warnings 'all';
    use SpecialHash;
    
    my %hash = (
        a => {
            key_a1 => 'value_a1',
            key_a2 => sub { 'value_a2' },
        },
        b => {
            key_b1 => sub { 'value_b1' },
            key_b2 => 'value_b2',
        },
    );
    
    # Tie all the secondary hashes that are hash references
    #
    for my $val ( values %hash ) {
        tie %$val, SpecialHash => ( %$val ) if ref $val eq 'HASH';
    }
    
    # Dump all the elements of the second-level hashes
    #
    for my $k ( sort keys %hash ) {
    
        my $v = $hash{$k};
        next unless ref $v eq 'HASH';
    
        print "$k =>\n";
    
        for my $kk ( sort keys %$v ) {
            my $vv = $v->{$kk};
            print "    $kk => $v->{$kk}\n" 
        }
    }
    

    output

    a =>
        key_a1 => value_a1
        key_a2 => value_a2
    b =>
        key_b1 => value_b1
        key_b2 => value_b2
    

提交回复
热议问题