I'd like to write a trait for an attribute that gives it a proxy so that I can manipulate the values behind the seens even for $!direct-access
within a class.
This is what I have right now, but as you can see, the actual get/set or store/fetch methods are never called. I'm probably not applying them correctly, but the only examples I've found have what looks like tons of extra code that are doing way more than I need but I can't isolate the important bits.
# The below shows on a Str more or less how I'd expect things to work
# although the %data wouldn't be hard coded of course
my Str $localized := do {
my %data = hi => "hola", bye => "adiós";
my $str = "";
Proxy.new:
:STORE{ $str = @_[1] },
:FETCH{ with %data{$str} { $_ } else { $str } }
}
$localized = "hi";
say $localized;
$localized = "bye";
say $localized;
$localized = "not defined";
say $localized;
# This seems to almost work,
multi trait_mod:<is>(Attribute:D $a, :$localized!) {
say $a.container.VAR.WHAT;
$a.container.VAR does role Localized {
has $!str;
method STORE($a) { say "store!"; $!str = $a }
method FETCH {say "fetch!"}
}
}
class Foo {
has Str $.text is localized;
}
my $foo = Foo.new;
say $foo.text, " <-- should be in Spanish";
$foo.text = "bye";
say $foo.text, " <-- should be in Spanish";
Although in this case the STORE
method is called correctly, the fetch method is not, and the gist of $foo.text
is Scalar+{Localized}.new
, which shows I'm not quite applying things correctly.
You need to arrange for the Proxy
to be bound into the attribute, so that there's a Proxy
there rather than a Scalar
container that is usually created by class initialization logic. This could be achieved by setting build logic (although you'll be overriding any initial default value if taking this approach), and using that to bind the attribute to a new Proxy
at each object creation:
multi trait_mod:<is>(Attribute:D $a, :$localized!) {
$a.set_build: -> \SELF, | {
$a.set_value: SELF, Proxy.new:
STORE => -> $, $val { say "store $val" },
FETCH => { say "fetch!"; 42 }
}
}
This then will call the FETCH
and STORE
callbacks (noting that FETCH
may be called for internal reasons, such as type checks, as well as those accesses that you directly see):
class C {
has $.attr is localized is rw;
}
my $c = C.new;
$c.attr = 'foo';
my $x = $c.attr;
This example demonstrates that it functions on attribute reads inside of the class too:
class C {
has $.attr is localized is rw;
method m() {
$!attr = 'foo';
my $x = $!attr
}
}
C.new.m;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With