Как заставить Perl умереть при чтении, но не при записи, несуществующих ключей в глубоком хеше? - PullRequest
3 голосов
/ 18 сентября 2010

Я использую динамические многоуровневые хэши, из которых я читаю данные, но также записываю данные.

Для меня типичной ошибкой является доступ к несуществующим ключам (опечатки, ревизии БД и т. Д.).Я получаю undef с, которые распространяются на другие части и вызывают проблемы.Я хотел бы die всякий раз, когда я пытаюсь прочитать несуществующий ключ, но все же можно добавлять новые ключи.

Итак, требуемое поведение:

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

Я ранееотправил аналогичный вопрос и получил отличные ответы.Мне особенно нравится принятый, который позволяет использовать обычный синтаксис хеша.Единственная проблема в том, что я не уверен, как легко обобщить это для глубоких хэшей, как в примере выше.

ps Я нахожу эту функцию действительно полезной, и мне интересно, что я что-то упустил, так как она не делаеткажутся очень популярными.Возможно, это не обычное чтение / запись из / в один и тот же хэш?

Ответы [ 6 ]

3 голосов
/ 18 сентября 2010

Если вы хотите обернуть чеки вокруг хэша, создайте подпрограмму для этого и используйте ее в качестве интерфейса:

 use 5.010;
 use Carp qw(croak);

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

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

Но теперь вы начинаете выглядеть как класс. Когда вам нужно специализированное поведение, начните писать объектно-ориентированные классы. Делай все, что тебе нужно. Думаю, это та часть, которую вам не хватает.

Проблема с привязкой к хеш-интерфейсу состоит в том, что люди ожидают, что хеш-синтаксис будет действовать как нормальный хеш. Когда вы измените это поведение, другим людям будет непросто понять, что происходит и почему.

3 голосов
/ 18 сентября 2010

Для меня уже поздно, поэтому я буду краток, но вы могли бы сделать это, используя функциональность tie - ваш хэш был бы представлен объектом под ним и реализовал функции, необходимые для взаимодействия с хешем.

Проверить perldoc -f tie ; Есть также множество классов на CPAN , на которые можно посмотреть, включая сам Tie :: Hash , который является хорошим базовым классом для связанных хешей, на котором вы можете построить, переопределяя несколько методов для добавления Ваша проверка ошибок.

2 голосов
/ 19 сентября 2010

Если вы не знаете, какие ключи может иметь хэш, воспользуйтесь одним из предложенных связанных хэшей или просто включите предупреждения.Имейте в виду, что связывание очень медленно , в девять раз медленнее, чем обычный хэш, и в три раза медленнее, чем объект.

Если у вас есть фиксированный набор возможных ключей, что вы хотите, это ограниченный хеш .Ограниченный хеш позволит вам получить доступ к заданному набору ключей и выдаст ошибку, если вы попытаетесь получить доступ к чему-либо еще.Это также может рекурсивно.Это гораздо быстрее, чем связывание.

В противном случае я бы предложил превратить ваши данные в объект с помощью методов, а не прямого доступа к хешу.Это медленнее, чем хеш или ограниченный хеш, но быстрее, чем связанный хеш.В 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");

Наконец, поскольку вы упомянули ревизии БД, если ваши данные читаются из базы данных, то вызахочет взглянуть на сопоставитель объектных отношений (ORM), чтобы сгенерировать классы и объекты и операторы SQL для вас. DBIx :: Class и Rose :: DB :: Object - два хороших примера.

2 голосов
/ 18 сентября 2010

При включенной прагме warnings вы получаете Use of uninitialized value in print at... предупреждений в двух строках, которые хотите умереть.

Итак, если вы сделаете warnings смертельным, тогда они умрут вместо этого:

use warnings FATAL => 'all';


Обновление

Основываясь на ваших комментариях, я предполагаю, что ваша общая проблема заключается в следующем:

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 /

0 голосов
/ 21 сентября 2010

Использовать DiveDie из Данные :: 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 голосов
/ 21 сентября 2010

re: ваш комментарий - hints on how to get the recursive effect к ответу Эфира tie.

Я не для слабонервных, но ниже приведен базовый примериз в одну сторону чтобы вы могли делать то, что вы хотите, используя 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 и даже стоит ли пытаться:)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...