检查签名是否接受捕获从调用者中删除属性值

use*_*829 4 raku

我有一个 java 称之为“bean”的类。我希望能够分阶段合并属性值,同时检查合并的任何内容是否与子方法 BUILD 签名相匹配:

    class A {
      has Int $.a;
      has Str $.b;
      submethod BUILD ( Int :$!a, Str :$!b ) { }
      my Signature $sig = ( A.^methods.grep: { .name eq 'BUILD' } )[0].signature;
      method fill-in ( *%args ) {
        say 'filling-in';
        my @args = %args.pairs;
        @args.unshift: self;
        say 'after unshift';
        self.raku.say;
        my Capture $arg-capture = @args.Capture;
        say 'after Capture';
        self.raku.say;
        unless $arg-capture ~~ $sig {
          warn '(warning)';
          return;
        }
        my Str $name;
        my %merged;
        say 'after check';
        self.raku.say;
        for self.^attributes -> Attribute $a {
          $name = $a.name.substr: 2, *;
          %merged{ $name } = ( $a.get_value: self ) // %args{ $name } // $a.type;
        }
        self.BUILD: |%merged.Capture;
      }
    }

    my A $a = A.new: b => 'init';
    $a.fill-in: a => 1;
Run Code Online (Sandbox Code Playgroud)

输出

捕获
后 unshift
A.new(a => Int, b => "init")
后填充
A.new(a => Int, b => "init")
检查后
A.new(a => 1, b => 力量)

如果@args.unshift: self更改为@args.unshift: A,则在 Capture 之后它会死亡

无法查找 A 类型对象中的属性...

我意识到我不需要这样做,因为填充代码只考虑类中存在的属性,但想知道在检查签名是否接受它时清除 Capture 调用者的值是否是预期的行为?

取消移动一次性实例 ( @args.unshift: A.new) 可以解决该行为。

Jon*_*ton 5

Raku 中的签名绑定从左到右通过参数进行工作,计算默认值(如果需要)、执行类型检查并绑定值。然后继续下一个参数。这是必需的,以便where子句和后面的默认值引用前面的参数,例如:

sub total-sales(Date $from, Date $to = $from) { ... }
Run Code Online (Sandbox Code Playgroud)

在属性绑定的情况下,这也会在参数处理后立即发生,因为在其中可能会写出如下内容:

submethod BUILD ( Int :$!a, Str :$!b = ~$!a ) { }
Run Code Online (Sandbox Code Playgroud)

当智能匹配签名时,它会通过创建对拥有该签名的代码对象的调用来工作,以便有一个地方可以解析和查找参数值(例如,在 中,Date $to = $from它需要存储并稍后解析)这$from)。该调用记录随后将被丢弃,您无需考虑它。

属性参数将绑定到作为调用者传递的对象中 - 即第一个参数。对于参数:$!b,行为与没有命名参数时相同b:使用默认值,在本例中是类型对象Str。因此,作为绑定的副作用,对象中的属性的清除是预期的。

进一步的结果是,由于绑定不是事务性的,因此如下所示:

method m(Int $!a, Int $!b) { }
Run Code Online (Sandbox Code Playgroud)

调用$obj.m(1, "42")会 update $!a,然后抛出异常。可能这不是您想要的语义。也许更好的是,因为无论如何你都.^attributes在这里跳舞,所以就这样做吧:

class A {
  has Int $.a;
  has Str $.b;
  method fill-in(*%args --> Nil) {
    my @attrs;
    my @values;
    for self.^attributes -> Attribute $attr {
        my $shortname = $attr.name.substr(2);
        if %args{$shortname}:exists {
            my $value = %args{$shortname};
            unless $value ~~ $attr.type {
                warn '(warning)';
                return;
            }
            @attrs.push($attr);
            @values.push($value);
        }
    }
    @attrs.map(*.get_value(self)) Z= @values;
  }
}

my A $a = A.new: b => 'init';
$a.fill-in: a => 1;
note $a;
Run Code Online (Sandbox Code Playgroud)

请注意,这也消除了编写样板的需要BUILD,更好的是,该方法fill-in现在可以提取到 arole并在 All The Beans 中使用。