【发布时间】:2018-09-07 17:55:10
【问题描述】:
我将继续探索 Perl6 微妙的实现细节。这次我在将自己的方法安装到角色中时遇到了问题。请系好安全带,开始我们的密码之旅。
这个想法是一个属性特征,它在它被组合成的类型对象上安装方法。问题最初是在私有方法上发现的,我希望将其安装在声明属性的角色中。那时我发现在某些条件下,无法调用从闭包中引用标量的生成方法!很可能是由于关闭在运行时丢失。但最令人困惑的一点是,它只发生在角色身上,而且只有一个角色正在消费另一个角色!
所以,这里是特征来源:
unit module trait-foo;
role FooClassHOW {...}
role FooAttr {
has $.base-name = self.name.substr(2);
method compose (Mu \type) {
callsame;
if (type.HOW ~~ Metamodel::ClassHOW) && (type.HOW !~~ FooClassHOW) {
type.HOW does FooClassHOW;
}
}
method install-method ( Mu \type ) {
my $attr = self;
type.^add_private_method(
"attr-{$attr.base-name}",
method { "by attr {$attr.name}" }
);
type.^add_method(
"pubattr-{$attr.base-name}",
method { "by attr {$attr.name} - public" }
);
type.^add_private_method(
"check-{$attr.base-name}",
method { "not using closure" }
);
}
}
role FooClassHOW {
method compose ( Mu \type ) {
for type.^attributes.grep( FooAttr ) -> $attr {
$attr.install-method( type );
type.^add_private_method(
"class-{$attr.base-name}",
method { "by class: attr {$attr.name}" }
);
}
nextsame;
}
}
role FooRoleHOW {
method compose ( Mu \type ) {
for type.^attributes.grep( FooAttr ) -> $attr {
$attr.install-method( type );
type.^add_private_method(
"role-{$attr.base-name}",
method { "by role: attr {$attr.name}" }
);
}
nextsame;
}
}
multi trait_mod:<is> (Attribute:D $attr, :$foo!) is export {
$attr does FooAttr;
given $*PACKAGE.HOW {
when Metamodel::ParametricRoleHOW {
$_ does FooRoleHOW unless $_ ~~ FooRoleHOW;
}
default {
$_ does FooClassHOW unless $_ ~~ FooClassHOW;
}
}
}
这里的关键点是install-method,它安装了一个公共方法pubattr-<attr>,和一个私有方法attr-<attr>、check-<attr>。 pubattr-、attr- 和 check- 之间的区别在于,前两个指的是它们的关闭,而后者没有。如果在各自的文件中定义了两个角色和一个类,会发生以下情况:
compose_method_inject.p6
#!/usr/bin/env perl6
use lib '.';
use trait-foo;
use compose-foorole;
class Foo does FooRole {
has $.fubar is foo;
method class-test {
say self!check-fubar;
say self!class-fubar;
say self!attr-fubar;
}
}
my $inst = Foo.new;
note "> Class";
$inst.class-test;
note "> BarRole";
$inst.bar-role-test;
note "> FooRole";
$inst.foo-role-test;
compose-foorole.pm6
unit package compose;
use trait-foo;
use compose-barrole;
role FooRole does BarRole is export {
has $.foo is foo;
method foo-role-test {
note FooRole.^candidates[0].^private_method_table;
say self!check-foo;
say self!role-foo;
say self!attr-foo;
}
}
compose-barrole.pm6
unit package compose;
use trait-foo;
role BarRole is export {
has $.bar is foo;
method bar-role-test {
note BarRole.^candidates[0].^private_method_table;
say self!check-bar;
say self!role-bar;
say self!inattr-bar;
}
}
执行 compose_method_inject.p6 会产生以下输出:
> Class not using closure by class: attr $!fubar by attr $!fubar by attr $!fubar - public > BarRole {attr-bar => <anon>, check-bar => <anon>, role-bar => <anon>} not using closure by role: attr $!bar Cannot invoke this object (REPR: Null; VMNull)
请注意,当 BarRole 中的类似代码失败时,该类工作正常。如果首先执行来自FooRole 的foo-role-test,将观察到相同的结果:
> Class not using closure by class: attr $!fubar by attr $!fubar by attr $!fubar - public > FooRole {attr-foo => <anon>, check-foo => <anon>, role-foo => <anon>} not using closure by role: attr $!foo Cannot invoke this object (REPR: Null; VMNull)
同样值得注意的是,从FooRoleHOW安装的方法并没有失去关闭,并且被成功执行。
现在,来另一个技巧。我从FooRole 中删除does BarRole 并将其直接应用于Foo:
class Foo does FooRole does BarRole {
输出发生巨大变化,情况变得更加混乱:
> Class not using closure by class: attr $!fubar by attr $!fubar by attr $!fubar - public > FooRole {attr-foo => <anon>, check-foo => <anon>, role-foo => <anon>} not using closure by role: attr $!foo by attr $!foo > BarRole {attr-bar => <anon>, check-bar => <anon>, role-bar => <anon>} not using closure by role: attr $!bar Cannot invoke this object (REPR: Null; VMNull)
UPD 另一个需要注意的重要事项是角色和类都故意按文件拆分,因为将它们全部放在公共文件中会使事情按预期工作。
顺便说一句,我不想更深入地研究它,但是在从中提取上述示例的原始代码中,我还使用.set_name 设置方法名称。名称是字符串,包括闭包中的 $attr 标量。 compose() 中的转储方法表正在生成以集合名称作为值的散列;在用户代码中转储相同的表显示与上述类似的输出 - 以 <anon> 作为值。看起来,方法名称与闭包一起被 GC'ed。
现在,我想听听有人说我很愚蠢,必须以不同的方式安装方法。或者关于属性的信息必须以其他方式保存,而不是通过依赖闭包。或者任何其他可以让我创建私有属性相关方法的想法。
【问题讨论】:
-
也许,报告一个错误是有意义的。
-
Cannot invoke this object (REPR: Null; VMNull)我一直在 Trait::Env for Attirbutes 中看到这个错误,原因似乎相同(属性方法中的闭包被丢弃)。启用预编译“修复”了该问题,因此看起来与此有关。我相信已经有一些与之相关的错误票。 -
那么还有一张关于这个主题的票... ;)
标签: raku