Let's say I have a class with several attributes but I only need some of them for object construction; the remaining attributes's values depend on these public attributes. However, I'd like to still access the remaining attributes's values with methods named after the attributes. From the top of my head, there are two options:
First option:
new
method. Or leave the new
method as is, and instead use a BUILD
submethod to both set the passed argument to the right attribute and set remaining attributes using this argument's value.class Foo {
has Int $.a;
has Int $.b;
has Int $.c;
submethod BUILD(:$!a) {
$!b = $!a ** 2;
$!c = $!a ** 3;
}
}
say Foo.new(:2a, :1b); #=> Foo.new(a => 2, b => 4, c => 8)
say Foo.new(:2a, :1b).b; #=> 8
Second option:
Declare with $.
only those attributes needed for object construction and modify the remaining attributes (declared with $!
) with a TWEAK
submethod after object construction. However, now I need to create accesor methods for these attributes.
class Bar {
has Int $.a;
has Int $!b;
has Int $!c;
submethod TWEAK {
$!b = $!a ** 2;
$!c = $!a ** 3;
}
method b { $!b }
method c { $!c }
}
say Bar.new(:2a, :1b); #=> Bar.new(a => 2)
say Bar.new(:2a, :1b).b; #=> 8
What's the best option? Are there any other options? What are the advantages/drawbacks for each option?
Personally I'd go nice and simple :
class Foo {
has Int $.a;
has Int $!b = $!a ** 2;
has Int $!c = $!a ** 3;
method b { $!b }
method c { $!c }
}
say Foo.new(:2a, :1b); #=> Foo.new(a => 2)
say Foo.new(:2a, :1b).b; #=> 4
Just use the default constructor and default values for the attributes and add a couple of basic read methods. The default constructor only updates public attributes so if you try and override b
or c
this is ignored.
If you wanted you could add a BUILD
submethod to error if someone tries and sets them.
A good point raised in the comments for this case (and possibly the final use case) doing it like this :
class Foo {
has Int $.a;
method b() is pure { $!a ** 2 }
method c() is pure { $!a ** 3 }
}
say Foo.new(:2a, :1b); #=> Foo.new(a => 2)
say Foo.new(:2a, :1b).b; #=> 4
Depending on the complexity of the calculation you may want to use the is cached
trait too.
I read an article a couple of weeks ago that talked about this ambiguity between the constructor arguments of a class and its public interface but I cannot find it anymore.
But I am thinking you could leverage FALLBACK
.
class Bar {
has Int $.a;
has Int $!b;
has Int $!c;
submethod TWEAK {
$!b = $!a ** 2;
$!c = $!a ** 3;
}
method FALLBACK( $name ) {
self.^attributes.first( *.name eq "\$!$name" ).?get_value(self);
}
}
say Bar.new(:2a);
say Bar.new(:2a).c;
That's kinda hacky though, and costly since the attribute lookups have to go though FALLBACK
and introspection. What would be nice to have is a trait that would create an accessor but have no effect on the constructor. Something like
class Bar {
has Int $.a;
has Int $.b is shady; # or whatever name
has Int $.c is shady;
}
But I am not aware such a thing exists.
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