【问题标题】:Traits, attributes, roles and closures特征、属性、角色和闭包
【发布时间】: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-&lt;attr&gt;,和一个私有方法attr-&lt;attr&gt;check-&lt;attr&gt;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 中的类似代码失败时,该类工作正常。如果首先执行来自FooRolefoo-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() 中的转储方法表正在生成以集合名称作为值的散列;在用户代码中转储相同的表显示与上述类似的输出 - 以 &lt;anon&gt; 作为值。看起来,方法名称与闭包一起被 GC'ed。

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

【问题讨论】:

  • 也许,报告一个错误是有意义的。
  • Cannot invoke this object (REPR: Null; VMNull) 我一直在 Trait::Env for Attirbutes 中看到这个错误,原因似乎相同(属性方法中的闭包被丢弃)。启用预编译“修复”了该问题,因此看起来与此有关。我相信已经有一些与之相关的错误票。
  • 那么还有一张关于这个主题的票... ;)

标签: raku


【解决方案1】:

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

该错误有一个解决方法。由于从类/角色作曲家安装的方法不会丢失它们的闭包,因此必须将方法的安装移入其中。在我的例子中,因为两者都需要类似的功能,所以使用角色实现方法安装程序很有魅力。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-04-26
    • 1970-01-01
    • 2011-12-01
    • 2015-10-24
    • 1970-01-01
    • 2019-09-03
    • 2020-04-21
    • 2021-12-24
    相关资源
    最近更新 更多