特征,属性,角色和闭包

Vad*_*man 5 perl6

我正在继续探索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;
         }
     }
 }
Run Code Online (Sandbox Code Playgroud)

这里的关键点是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;
Run Code Online (Sandbox Code Playgroud)

撰写-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;
     }
 }
Run Code Online (Sandbox Code Playgroud)

撰写-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;
     }
 }
Run Code Online (Sandbox Code Playgroud)

执行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)
Run Code Online (Sandbox Code Playgroud)

请注意,该类工作正常,而类似的代码BarRole失败.如果首先执行foo-role-testfrom,FooRole则会观察到相同的结果:

> 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)
Run Code Online (Sandbox Code Playgroud)

还值得注意的是,安装的方法FooRoleHOW不会失去其关闭并且已成功执行.

现在,另一招.我删除does BarRoleFooRole,使之直接应用于富:

class Foo does FooRole does BarRole {
Run Code Online (Sandbox Code Playgroud)

输出变化很大,情况变得更加混乱:

> 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)
Run Code Online (Sandbox Code Playgroud)

UPD另一个需要注意的重要事项是,角色和类有意地按文件分割,因为将它们全部放在公共文件中会使事情按预期工作.

顺便说一句,我不想​​深入了解它,但在原始代码中,上面提取的样本我也设置了方法名称.set_name.名称是字符串,包括$attr闭包的标量.compose()正在生成哈希的转储方法表,其中集合名称为值; 在用户代码中转储相同的表显示类似于上面的输出 - 使用<anon>as值.看起来,方法名称与封闭一起进行了GC.

现在,我想听一些人说我很蠢,方法必须以不同的方式安装.或者有关属性的信息必须以其他方式保留,而不是依赖于闭包.或任何其他想法,让我创建私有属性相关的方法.

Vad*_*man 2

这并不完全是答案,而是该错误的注释和解决方法。好的,刚刚做了说明:这是一个错误。虽然它不存在于 Linux 版本的 rakudo 中,但我只在 macOS/darwin 上观察到它。当然,这并不意味着其他平台就不会受到攻击。

该错误有一个解决方法。由于从类/角色编辑器安装的方法不会丢失其闭包,因此必须将方法的安装移至其中。就我而言,因为两者都需要类似的功能,所以使用角色实现方法安装程序是一种魅力。