Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Apply a proxy using traits

Tags:

raku

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.

like image 203
user0721090601 Avatar asked Aug 30 '19 23:08

user0721090601


1 Answers

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;
like image 142
Jonathan Worthington Avatar answered Oct 25 '22 11:10

Jonathan Worthington