Konrad Eisele
Konrad Eisele

Reputation: 3194

How to access attributes in object dynamically in Raku

I wonder howto access an object's attribute dynamically via a name as a Str at runtime in Raku. Instead of:

#!/usr/bin/rakudo                                                                                                                                                                    
                                                                                                                                                                                    
class c0 {                                                                                                                                                                          
    has $!a0 = 1;                                                                                                                                                                      
    has $!a1 = 2;                                                                                                                                                                      
    method access(Str $m) {
        if ($m eq "a0") { return $!a0; }                                                                                                                                                
        if ($m eq "a1") { return $!a1; }                                                                                                                                                
    }
    method set(Str $m, $v) {
        if ($m eq "a0") { $!a0 = $v; }                                                                                                                                                
        if ($m eq "a1") { $!a1 = $v; }                                                                                                                                                
    }                                                                                                                                                                               
}                                                                                                                                                                                   
                                                                                                                                                                                    
my $c = c0.new();                                                                                                                                                                   
$c.set("a0", 3);
$c.set("a1", 4);
say $c.access("a0");                                                                                                                                                                    
say $c.access("a1");       

I would like to use something that would look in pseudocode:

class c0 { 
    ...
    method access(Str $m) {
        return self.$m;   
    }                                                                                                                                                                               
    method set(Str $m, $v) {
        self.$m = $v;   
    }                                                                                                                                                                               
}                                                                                                                                                             
              

Is this possible in Raku? Which construct do I need to use?

As a backgrounder I was thinking how to implement a role that adds associativity functionality to the class, to transparently access a member. The attribute name would be parametrized: If I have a class class ports { has @!ports_; ... } and an instance my $p = ports.new() then I want to be able to use the subscript syntax to access @ports_ via $p[...] . I try to figure out weather I can define role acc [ Str $member] does Associative[Cool,Str] { ... } and then define ports via class ports does acc["ports_"] { ... } where the AT-KEY and EXISTS-KEY in role acc are implemented using dynamic attribute access (if that is possible). I dont want to use "EVAL".

Upvotes: 8

Views: 256

Answers (2)

Brad Gilbert
Brad Gilbert

Reputation: 34120

You changed your post to add a question about how to do something like this:

role acc [ Str $member] does Associative[Cool,Str] { ... }

class ports does acc["ports_"] { has @!ports_; ... }

The answer is of course, don't do that.
I mean you can, but you really shouldn't.
I mean you really really shouldn't.

Also you indicate that you want to use [] for indexing. The thing is that is Positional not Associative.

(I'm ignoring the fact that there is no point to add _ to the end of the attribute name. Usually in Perl or Python adding _ indicated private, but we don't need to do that in Raku.)


The right way to do that is to have the array inside of the role.

role Array::Access [::OF = Cool] does Positional[OF] {
  has OF @!array-access handles < AT-POS >;
}

class Ports does Array::Access {
  # allows you to access it as self!ports inside of this class
  method !ports () is raw { @!array-access }
}

Which shows that adding a role to do that is probably overkill.

class Ports does Positional[Cool] {
  has Cool @!ports handles < AT-POS >;
}

If you really, really want to do it they way you asked for, the following works.

role Inner::Array::Access [ Str:D \name, ::OF = Cool ] does Positional[OF] {
  # a way to quickly access the attribute
  # (hopefully no-one tries to add an attribute of this name to their class)
  has $!inner-array handles < AT-POS >;

  # set $!inner-array
  submethod TWEAK (){
    $!inner-array := self.^attributes.first(name).get_value(self);
  }
}

class Ports does Inner::Array::Access['@!ports'] {
  has @!ports;

  # a quick way to add a way to set @!ports for our test
  submethod BUILD( :@!ports ){}
}

my Ports $v = ports => [0,10,20,30];

say $v[2]; # 20

Probably what you were thinking is embed the self.^attributes thing into AT‍-‍POS.

role Inner::Array::Access [ Str:D \name, ::OF = Cool ] does Positional[OF] {
  method AT-POS ( \index ) is raw {
    self.^attributes.first(name).get_value(self).AT-POS(index);
  }
}

That would be slow, because it has to do all of those lookups everytime you access a single element.

Upvotes: 4

Tyil
Tyil

Reputation: 1807

This is possible with some introspection of the attributes. However, I would like to point out that it is the exact intention of private attributes to be private. Creating a workaround to handle them as public attributes is an anti-pattern, and introduces needless complexity.

class c0 {
    has $.a0 = 1;
    has $.a1 = 2;

    method access (Str $m) {
        my $attribute = self.^attributes.first({ ~$_ eq '$!' ~ $m });

        return unless $attribute;

        $attribute.get_value(self); # 1
    }
}

my $c = c0.new;

say $c.access('a0');

For setting the value, you can use the .set_value method on the attribute.

method set (Str $m, $v) {
    ...

    $attribute.set_value(self, $v);
}

Old answer left here for historic purposes.

Yes, something like this is possible in Raku. You don't even need to explicitly define the access method.

class c0 {
    has $.a0 = 1;
    has $a.1 = 2;
}

my $c = $c0.new;

say $c.'a0'(); # 1

This works because Raku creates an accessor method for public variables for your classes, which is called when you use .'a0'(). The () are required for using a quoted method name.

Upvotes: 5

Related Questions