动态变量是一种很好的方式,它可以获得全局变量的好处,但又不存在缺点。它们在调用树上传递信息,而不强制调用者进行声明。然而,动态变量与异常共享一个负担。被调用者知道如何做调用者可能想不到的事情。
use Module::By::Author::A;
use Module::By::Author::B;
my $*dynvar = 42;
sub-from-A(); # expects $*dynvar to be Int
sub-from-B(); # expects $*dynvar to be IO(Str)
sub-from-C(); # expects $*dynvar to be IO::Handle
在这个例子中,sub-from-B()
将默默地失败,直到它试图打开名为 “42” 的文件。而 sub-from-C()
会试图把 42 强转成为一个文件句柄并抛出。因此,在预期由独立模块设置的 dynvars 中存在一个危险。更糟糕的是,这种行为可能会在任何 zef --install
之后突然出现。Raku 是一种动态语言,它会尽力自动转换值,并在运行时失败。通过提前失败来支持编译器是个好建议。
我想在一些模块中使用 dynvars 来提供半全局标志,以去除模板。下面的内容似乎可以提供保护,防止 dynvar 无意间的溢出。
class Switch is Mu {
has $.name;
method gist { $.name }
method Str { die('invalid coersion') }
}
constant on = Switch.new: :name<on>;
constant off = Switch.new: :name<off>;
sub s() {
# put $*d; # this will die
say $*d;
dd $*d;
}
my $*d = off;
s();
# OUTPUT: off
# Switch $*d = Switch.new(name => "off")
我从 Mu
中派生出 Cool
中的所有 coercers,并重载 Str
,把那个也从循环中取出来。除了调试之外,使用 say
是一个 bug,所以我可能会用 .gist
适当地支持它。由于我用类创建了一个类型,所以我可以用 whereception 去保护我的 sub 和方法。
sub dyn-var-typecheck(Mu:U \T, $name) {
sub {
DYNAMIC::($name) ~~ T || die(„$name is set to an unexpected value“)
}
}
constant &dyn-s-typecheck = dyn-var-typecheck(Switch, ‚$*d‘);
sub s($? where dyn-s-typecheck) { }
# OUTPUT: $*d is set to an unexpected value
# in sub at /home/dex/projects/blog/shielded-dynvars.raku line 6
# in sub s at /home/dex/projects/blog/shielded-dynvars.raku line 22
# in block <unit> at /home/dex/projects/blog/shielded-dynvars.raku line 29
在 where
子句中使用 DYNAMIC::($name)
会减慢对 MMD 的任何调用。所以把检查拉到子例程中可能是合理的。
通过这个措施,我觉得在 Shell::Piping 中添加 $*always-capture-stderr
更好,可以在我启动的几乎所有管道上摆脱 :stderr(Capture)
。而在各处添加 $*debug
时,我感觉好多了。
Raku 在设计时并没有考虑到对 dynvars 的类型检查。它被设计成 Perl。这让我们可以灵活地在问题发生时进行修复。
原文链接: https://gfldex.wordpress.com/2020/08/14/guarding-dynamics/