How can I make Perl die while reading, but not writing, to non-existent keys in a deep hash?

I use dynamic multi-level hashes from which I read data, but also write data.

A common mistake for me is access to non-existent keys (typos, db versions, etc.). I get undefthat apply to other parts and cause problems. I would like diewhenever I try to read a non-existent key, but I am still allowed to add new keys.

So the desired behavior:

my %hash;
$hash{A} = 5;  # ok
print $hash{A}, "\n";  # ok
print $hash{X}, "\n";  # should die
$hash{B}{C}{D} = 10; # ok 
print $hash{B}{C}{X}, "\n";  # should die

I posted a similar question earlier and got great answers. I especially like the accepted one, which allows you to use the usual hash syntax. The only problem is that I'm not sure how easy it is to generalize this to deep hashes, as in the example above.

ps I think this feature is really useful, and I wonder if I am missing something because it is not very popular. Maybe it is not uncommon to read / write from / to the same hash?

+3
source share
6 answers

With warningspragma enabled, then you get warnings Use of uninitialized value in print at...on two lines that you want to die.

So, if you make warningsfatal, then they will die instead:

use warnings FATAL => 'all';


Update

, , - - :

my $x = $hash{B}{C}{X};

/, $x .

, :

my $x = $hash{B}{C}{X} // 'some default value';

my $z = $hash{B}{C}{Z} // die "Invalid hash value";

, :(

, , :

use 5.012;
use warnings FATAL => 'all';
use Carp 'croak';

# Value Or Croak!
sub voc { $_[0] // croak "Invalid hash" }

!

my $x = voc $hash{B}{C}{X};

, , .

/I3az/

+2

, , tie - , , , .

perldoc -f tie; CPAN, , Tie:: , , , , .

+3

, :

 use 5.010;
 use Carp qw(croak);

 sub read_from_hash {
     my( $hash, @keys ) = @_;

     return check_hash( $hash, @keys ) // croak ...;
     }

. , - . , . , .

- , , . , , .

+3

, , - . , , , .

, . , - . . , .

, -. , , . CPAN , Class::Accessor.

, get() set() :

package Safe::Hash;

use strict;
use warnings;
use Carp;

sub new {
    my $class = shift;
    my $self = shift || {};
    return bless $self, $class;
}

sub get {
    my($self, $key) = @_;
    croak "$key has no value" unless exists $self->{$key};
    return $self->{$key};
}

sub set {
    my($self, $key, $value) = @_;
    $self->{$key} = $value;
    return;
}

, .

my $inner = Safe::Hash->new({ foo => 42 });
my $outer = Safe::Hash->new({ bar => 23 });
$outer->set( inner => $inner );
print $outer->get("inner")->get("foo");

, db-, , (ORM) SQL. DBIx::Class Rose::DB::Object - .

+2

re: - hints on how to get the recursive effect Ether tie answer.

, - , , , Tie::Hash:

HashX.pm

package HashX;
use 5.012;
use warnings FATAL => 'all';
use Carp 'croak';
use Tie::Hash;
use base 'Tie::StdHash';

sub import {
    no strict 'refs';
    *{caller . '::hash'} = sub {
        tie my %h, 'HashX', @_;
        \%h;
    }
}

sub TIEHASH {
    my $class = shift;
    croak "Please define a structure!" unless @_;
    bless { @_ }, $class;
}

sub STORE {
    my ($self, $key, $value) = @_;
    croak "Invalid hash key used to store a value" unless exists $self->{$key};
    $self->{$key} = $value;
}

sub FETCH {
    my ($self, $key) = @_;
    exists $self->{$key} 
        ?  $self->{$key} 
        :  croak "Invalid hash key used to fetch a value";
}

1;

. , FETCH STORE croak, -.

hash, tie .

use 5.012;
use warnings;
use HashX;

# all my hashref are ties by using hash()
my $hash = hash(
    a => hash(
        b => hash(
            c => undef,
        ),
    ),
);

$hash->{a}{b}{c} = 1;      # ok
$hash->{a}{b}{c} = 2;      # also ok!
$hash->{a}{b}{d} = 3;      # throws error
my $x = $hash->{a}{b}{x};  # ditto

, . , , , Tie::Hash :)

0

Use DiveDie from Data :: Diver :

use Data::Diver qw(DiveDie);

my $href = { a => { g => 4}, b => 2 };

print DiveDie($href, qw(a g)), "\n"; # prints "4"
print DiveDie($href, qw(c)), "\n";   # dies
0
source

Source: https://habr.com/ru/post/1765404/


All Articles