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



我正在尝试使用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模块;如果需要的话,我可以写自己的X,一切都会简单得多,但是我想避免。

store被调用时,将哈希元素作为lvalue获取时,因此 $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

扩展这一点不需要太多,即可递归地添加魔术。请参阅文档中的"递归铸造魔术"食谱。

最新更新