我正在尝试使用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模块;如果需要,我可以编写自己的XS,使一切都变得简单得多,但我正在努力避免这种情况。
答案 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中的“对数据结构进行递归魔术”食谱。