我有一个 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) 可以解决该行为。
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 中使用。