在为 Shell::Piping 添加 dynvars 以减少手指受伤的风险时,我犯了一个错,lizmat 好心地纠正了这个错误。她建议使用 defined-or 操作符来测试一个给定的动态变量是否被声明。
($*FOO // '') eq 'yes'
这不等同于测试 dynvar 是否在调用树下声明。为此,我们需要检查 CALLERS。
say CALLERS::<$*colored-exceptions>:exists;
dd CALLERS::<$*colored-exceptions>;
# OUTPUT: False
# Nil
如果声明了 dynvar,我们会得到不同的结果。
sub dyn-receiver {
say CALLERS::<$*colored-exceptions>:exists;
dd CALLERS::<$*colored-exceptions>;
}
my $*colored-exceptions;
dyn-receiver();
# OUTPUT : True
# Any $*colored-exceptions = Any
对于一个模块的作者来说,这意味着我们可以让别人偷偷地把一些未定义的值放到我们使用的 dynvar 中,而这个 dynvar 的类型是我们没有想到的。可组合性和正确性是不一样的。如果我们想正确地处理这种情况,我们需要检查调用者是否声明了 dynvar,如果没有,则使用一个合适的默认值。
class Switch {
has $.name;
method gist { $.name }
method Str { die('invalid coersion') }
method Bool { die('invalid coersion') }
}
constant on is export := Switch.new: :name<on>;
constant off is export := Switch.new: :name<off>;
sub dyn-receiver {
my $*colored-exceptions = CALLERS::<$*colored-exceptions>:exists
?? CALLERS::<$*colored-exceptions>
!! off
}
在这个例子中,只有两个可能的值,但如果有更多的值,而且它们可能是未定义的,我们需要更加小心。然而,这是个相当大的类型。我们可以在这里使用 deboilerplater 吗?
sub infix:<///>(\a, \b) is raw {
my $dyn-name = a.VAR.name;
my $has-outer-dynvar = CALLER::CALLERS::{$dyn-name}:exists;
CALLER::{$dyn-name} = $has-outer-dynvar ?? CALLER::CALLERS::{$dyn-name} !! b
}
sub c {
my $*colored-exceptions /// Int;
dd $*colored-exceptions;
}
sub d {
my $*colored-exceptions = Str;
c();
}
c();
d();
# OUTPUT: Int $*colored-exceptions = Int
Str $*colored-exceptions = Str
这个操作符需要两个约束参数。如果我们用 dynvar 调用它,则 a 包含 dynvar 的容器。我们可以查询这个容器的名称,然后用它来检查在调用树的下面是否已经声明了dynvar。如果是,我们使用它的值,并将其直接赋值到 c 中声明的 dynvar 中。在这两种情况下,我们都有可能返回一些调皮的东西,所以我们最好做的是原始的。
戳穿栈是有风险的。这可以通过适当的宏来实现。我很确定我们可以在圣诞节后这样做(对于任何大于然后去年圣诞节的值)。
原文链接: https://gfldex.wordpress.com/2020/08/17/defined-or-dynvar/