Automatically call hash values that are subroutine references

前端 未结 7 2063
孤街浪徒
孤街浪徒 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:34

    One of perl's special features for just such a use case is tie. This allows you to attach object oriented style methods, to a scalar or hash.

    It should be used with caution, because it can mean that your code is doing really strange things, in unexpected ways.

    But as an example:

    #!/usr/bin/env perl
    
    package RandomScalar;
    
    my $random_range = 10;
    
    sub TIESCALAR {
        my ( $class, $range ) = @_;
        my $value = 0;
        bless \$value, $class;
    }
    
    sub FETCH {
        my ($self) = @_;
        return rand($random_range);
    }
    
    sub STORE {
        my ( $self, $range ) = @_;
        $random_range = $range;
    }
    
    package main;
    
    use strict;
    use warnings;
    
    tie my $random_var, 'RandomScalar', 5;
    
    for ( 1 .. 10 ) {
        print $random_var, "\n";
    }
    
    $random_var = 100;
    for ( 1 .. 10 ) {
        print $random_var, "\n";
    }
    

    As you can see - this lets you take an 'ordinary' scalar, and do fruity things with it. You can use a very similar mechanism with a hash - an example might be to do database lookups.

    However, you also need to be quite cautious - because you're creating action at a distance by doing so. Future maintenance programmers might well not expect your $random_var to actually change each time you run it, and a value assignment to not actually 'set'.

    It can be really useful for e.g. testing though, which is why I give an example.

    In your example - you could potentially 'tie' the hash:

    #!/usr/bin/env perl
    
    package MagicHash;
    
    sub TIEHASH {
        my ($class) = @_;
        my $self = {};
        return bless $self, $class;
    }
    
    sub FETCH {
        my ( $self, $key ) = @_;
        if ( ref( $self->{$key} ) eq 'CODE' ) {
            return $self->{$key}->();
        }
        else {
            return $self->{$key};
        }
    }
    
    sub STORE {
        my ( $self, $key, $value ) = @_;
        $self->{$key} = $value;
    }
    
    sub CLEAR {
        my ($self) = @_;
        $self = {};
    }
    
    sub FIRSTKEY {
        my ($self) = @_;
        my $null = keys %$self;    #reset iterator
        return each %$self;
    }
    
    sub NEXTKEY {
        my ($self) = @_;
        return each %$self;
    }
    
    package main;
    
    use strict;
    use warnings;
    use Data::Dumper;
    
    tie my %magic_hash, 'MagicHash';
    %magic_hash = (
        key1 => 2,
        key2 => sub { return "beefcake" },
    );
    
    $magic_hash{random} = sub { return rand 10 };
    
    foreach my $key ( keys %magic_hash ) {
        print "$key => $magic_hash{$key}\n";
    }
    foreach my $key ( keys %magic_hash ) {
        print "$key => $magic_hash{$key}\n";
    }
    foreach my $key ( keys %magic_hash ) {
        print "$key => $magic_hash{$key}\n";
    }
    

    This is slightly less evil, because future maintenance programmers can use your 'hash' normally. But dynamic eval can shoot the unwary in the foot, so still - caution is advised.

    And alternative is to do it 'proper' object oriented - create a 'storage object' that's ... basically like the above - only it creates an object, rather than using tie. This should be much clearer for long term usage, because you won't get unexpected behaviour. (It's an object doing magic, which is normal, not a hash that 'works funny').

    0 讨论(0)
提交回复
热议问题