Defined or Dynvar

楽土

在为 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/

comments powered by Disqus