Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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".

like image 529
Konrad Eisele Avatar asked Jul 23 '20 13:07

Konrad Eisele


People also ask

What about dynamic ABAP Objects?

One of the outstanding features of ABAP is the capability of dynamic programming. In particular, you can use What about dynamic ABAP Objects? Well, apart from program generation all dynamic features listed above are readily available in ABAP Objects too. Especially, you can use: • Dynamic attribute access via field symbols:

How to access the object property dynamically in JavaScript?

To access the object property dynamically in JS, you need to pass the property name as a string in square brackets. As the value of the variable key changed, we got access to the different properties of the user object. The most common way to access the object properties in JavaScript is the dot.

How do you call an OREF from a dynamic class?

CALL METHOD oref- > (‘METH’). Dynamic subroutine pool with a local class. The dynamic class implements a global interface that is used to type the reference variable oref. Instead of an interface you could also inherit from a global super class to achieve static typing of the reference variable.

How to access the local classes of a dynamic subroutine pool?

Remains only the question how to access the local classes or interfaces of a dynamic subroutine pool. One feasable way would be to create exactly one subroutine (as a factory) in the subroutine pool that wraps the access to ABAP Objects. Another way includes ingredient 2. Then, there is no need for subroutines at all.


2 Answers

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.

like image 62
Tyil Avatar answered Sep 19 '22 20:09

Tyil


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.

like image 42
Brad Gilbert Avatar answered Sep 19 '22 20:09

Brad Gilbert