我正在尝试使用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::wizard
到mask
的一些选项,但我没有找到任何东西。
注意:我不在乎使用重量重量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
扩展这一点不需要太多,即可递归地添加魔术。请参阅文档中的"递归铸造魔术"食谱。