使用哈希或数组元素时如何绕过魔术钩

时间:2019-02-18 07:41:40

标签: perl

我正在尝试使用Variable::Magic来捕获哈希的元素何时被修改:

use Variable::Magic qw(cast wizard);
my %h = (a => 1, b => 2);
cast %h, wizard store => sub {
    warn "store: @_\n";
    my $k = $_[2];
    cast $_[0]{$k}, wizard set => sub {
        warn "$k set to ${$_[0]}\n"
    }
};
$h{a} = 33;

但是,哈希元素上的第二个内部cast将触发哈希中的store魔术,并进行无限递归(并崩溃)。

我发现的唯一方法是将通过cast附加的数据用作锁/标志:

use Variable::Magic qw(cast wizard);
cast %h, wizard
    data => sub {0},
    store => sub {
        return if $_[1]++;
        my $k = $_[2];
        cast $_[0]{$k}, wizard set => sub {
            warn "$k set to ${$_[0]}\n"
        };
        $_[1] = 0;
    }
;
$h{a} = 33;

这看起来笨拙而愚蠢,我觉得我缺少明显的东西。

有没有更好的方法?我一直在寻找一些方法来Variable::Magic::wizardmask自己的方法中的魔法,但是我什么也没找到。

注意:我不在乎使用重量级的tie或其他一些无法通过简单的apt-get安装的XS模块;如果需要,我可以编写自己的XS,使一切都变得简单得多,但我正在努力避免这种情况。

1 个答案:

答案 0 :(得分:2)

当将哈希元素作为左值提取时,会调用

store,因此$h{$k}=…f($h{$k})\$h{$k}会调用store。因此,虽然在更改值之后调用set,但在更改值之前必须调用store

实际上,它是在元素被提取之前就被调用的,因此,在元素不存在时甚至会在元素被创建之前被调用。这很不幸,因为这意味着仅进行简单的exists检查是不够的。

由于魔术回调被称为“在错误的时间”(当元素甚至不存在时),所以我能设计出像您这样的解决方案(即使用标志)。

也就是说,即使发生异常,也应确保将标志重置。可以使用local完成。


您的目标似乎是为哈希的每个元素添加一些魔术,包括稍后将创建的那些魔术。为此,我将使用以下内容:

use Variable::Magic qw( cast wizard );

my $hash_ele_wiz = wizard(
    set => sub {
        my ($ref) = @_;
        say "store: $$ref";
    },
);

my $hash_wiz = wizard(
    data => sub {
        my ($var) = @_;
        cast($_, $hash_ele_wiz) for values(%$var);
        return { nomg => 0 };
    },
    store => sub {
        my ($var, $data, $key) = @_;
        return if exists($var->{$key}) || $data->{nomg};
        my $ele_ref = do { local $data->{nomg} = 1; \( $var->{$key} ) };
        cast($$ele_ref, $hash_ele_wiz);
    },
);

my %h = ( a => 1, b => 2 );
cast(%h, $hash_wiz);

$h{b} = 3;          # store: 3
$h{c} = 4;          # store: 4
my $ref = \$h{d};
$$ref = 5;          # store: 5

扩展此功能以递归添加魔术不会花费太多。请参阅docs中的“对数据结构进行递归魔术”食谱。