Reputation: 3194
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
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
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