I'm trying to find how the binding operation works on attributes and what makes it so different from nqp::bindattr
. Consider the following example:
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;
It prints:
$["a", "b", "c"]
Replacing nqp::bindattr
with the binding operator from the comment gives correct output:
["a", "b", "c"]
Similarly, if foo
is a public attribute and accessor is used the output would be correct too due to deconterisation taking place within the accessor.
I use similar code in my AttrX::Mooish
module where use of :=
would overcomplicate the implementation. So far, nqp::bindattr
did the good job for me until the above problem arised.
I tried tracing down Rakudo's internals looking for :=
implementation but without any success so far. I would ask here either for an advise as to how to simulate the operator or where in the source to look for its implementation.
Before I dig into the answer: most things in this post are implementation-defined, and the implementation is free to define them differently in the future.
To find out what something (naively) compiles into under Rakudo Perl 6, use the --target=ast
option (perl6 --target=ast foo.p6
). For example, the bind in:
class C {
has $!a;
submethod BUILD() {
my $x = [1,2,3];
$!a := $x
}
}
Comes out as:
- QAST::Op(bind) :statement_id<7>
- QAST::Var(attribute $!a) <wanted> $!a
- QAST::Var(lexical self)
- QAST::WVal(C)
- QAST::Var(lexical $x) $x
While switching it for @!a
like here:
class C {
has @!a;
submethod BUILD() {
my $x = [1,2,3];
@!a := $x
}
}
Comes out as:
- 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)
The decont
instruction is the big difference here, and it will take the contents of the Proxy
by calling its FETCH
, thus why the containerization is gone. Thus, you can replicate the behavior by inserting nqp::decont
around the Proxy
, although that rather begs the question of what the Proxy
is doing there if the correct answer is obtained without it!
Both :=
and =
are compiled using case analysis (namely, by looking at what is on the left hand side). :=
only works for a limited range of simple expressions on the left; it is a decidedly low-level operator. By contrast, =
falls back to a sub
call if the case analysis doesn't come up with a more efficient form to emit, though in most cases it manages something better.
The case analysis for :=
inserts a decont
when the target is a lexical or attribute with sigil @
or %
, since - at a Perl 6 level - having an item bound to an @
or %
makes no sense. Using nqp::bindattr
is going a level below Perl 6 semantics, and so it's possible to end up with the Proxy
bound directly there using that. However, it also violates expectations elsewhere. Don't expect that to go well (but it seems you don't want to do that anyway.)