私有属性的绑定:nqp::bindattr vs :=

Binding of private attributes: nqp::bindattr vs :=

我正在尝试了解绑定操作如何作用于属性以及它与 nqp::bindattr 有何不同。考虑以下示例:

 class Foo {
     has @!foo;

     submethod TWEAK {
         my $fval = [<a b c>];
         use nqp;
         nqp::bindattr( nqp::decont(self), $?CLASS, '@!foo',
         #@!foo :=
             Proxy.new(
                 FETCH => -> $ { $fval },
                 STORE => -> $, $v { $fval = $v }
             )
         );
     }

     method check {
         say @!foo.perl;
     }
 }

 my $inst = Foo.new;
 $inst.check;

它打印:

$["a", "b", "c"]

用注释中的绑定运算符替换 nqp::bindattr 可得到正确的输出:

["a", "b", "c"]

类似地,如果 foo 是一个 public 属性并且使用了访问器,那么由于访问器中发生了去中心化,因此输出也是正确的。

我在我的 AttrX::Mooish 模块中使用了类似的代码,其中使用 := 会使实现过于复杂。到目前为止,nqp::bindattr 为我做了很好的工作,直到出现上述问题。

我试图追踪 Rakudo 的内部结构以寻找 := 实现,但到目前为止没有任何成功。我会在这里询问有关如何模拟运算符或在源代码中的何处寻找其实现的建议。

在我深入研究答案之前:post 中的大部分内容都是实现定义的,实现可以在未来自由地以不同方式定义它们。

要找出某些东西(天真地)在 Rakudo Perl 6 下编译成什么,请使用 --target=ast 选项 (perl6 --target=ast foo.p6)。例如,绑定在:

class C {
    has $!a;
    submethod BUILD() {
        my $x = [1,2,3];
        $!a := $x
    }
}

结果为:

                              - QAST::Op(bind)  :statement_id<7>
                                - QAST::Var(attribute $!a) <wanted> $!a
                                  - QAST::Var(lexical self) 
                                  - QAST::WVal(C) 
                                - QAST::Var(lexical $x)  $x

在为 @!a 切换时,如下所示:

class C {
    has @!a;
    submethod BUILD() {
        my $x = [1,2,3];
        @!a := $x
    }
}

结果为:

                              - QAST::Op(bind)  :statement_id<7>
                                - QAST::Var(attribute @!a) <wanted> @!a
                                  - QAST::Var(lexical self) 
                                  - QAST::WVal(C) 
                                - QAST::Op(p6bindassert) 
                                  - QAST::Op(decont) 
                                    - QAST::Var(lexical $x)  $x
                                  - QAST::WVal(Positional) 

decont 指令是这里最大的区别,它将通过调用 FETCH 来获取 Proxy 的内容,这就是容器化消失的原因。因此,您可以通过在 Proxy 周围插入 nqp::decont 来复制该行为,尽管如果没有它就获得了正确答案,这反而回避了 Proxy 在那里做什么的问题!

:== 都是使用案例分析编译的(即,通过查看左侧的内容)。 := 仅适用于左侧有限范围的简单表达式;它是一个绝对低级的运营商。相比之下,如果案例分析没有提出更有效的形式来发出,= 会回退到 sub 调用,尽管在大多数情况下它管理得更好。

:= 的案例分析在目标是带有印记 @% 的词法或属性时插入 decont,因为 - 在 Perl 6 级别- 将项目绑定到 @% 没有任何意义。使用 nqp::bindattr 将比 Perl 6 语义低一个级别,因此 可能 最终使用 Proxy 直接绑定到那里。但是,它也违反了其他地方的预期。不要指望一切顺利(但看起来你无论如何也不想这样做。)