I have a method in a class to return a hash, with the aim that children classes will over-ride the method. But for testing purposes, I want a different hash.
When the class is instantiated, I would prefer for the test option to be assumed to be False, so :test
can be omitted when not needed.
At present I do this:
class A {
has %.tmps;
submethod BUILD( :$test = False ) {
%!tmps = $test ?? self.test-tmps !! self.default-tmps
}
method test-tmps { ... }
method default-tmps { ... }
}
This does what I want, in that my A $x .= new
calls default-tmps, and my A $y .=new(:test)
calls test-tmps.
But I wondered about removing the explicit check of $test via multi-dispatch. Except I could not work out the appropriate syntax. I tried
class A {
has %.tmps;
submethod BUILD( :$test = False ) {
%!tmps = self.get-tmps( :$test )
}
multi method get-tmps( :test($)! where * ) {
# return the hash for test values
}
multi method get-tmps( :test($)! where ! * ) {
# return the hash for default values
}
}
This does not work because I always get the test values, whether or not I specify :test
in new
. So my questions:
How to select the multi method candidate based solely on a boolean's value?
If what I am trying to do is possible, is there a reason (eg. run time / compile time checks) why an explicit check in BUILD
, called by new
, would be better than multi dispatch candidate selection?
And then, if multi-dispatch works, would the following work, or would $!test
be False because its undefined when %.tmps
is built?
class A {
has Bool $!test;
has %.tmps = self.get-tmps( :$!test );
submethod BUILD( :$!test = False ) {}
multi method get-tmps( :test($)! where * ) { ... }
# replacing the following by whatever is the correct syntax
multi method get-tmps( :test($) where ! * ) { ... }
}
First of all, nowadays I would recommend using the TWEAK
method, rather than the BUILD
method. The TWEAK semantics are generally more DWIM.
Secondly, there is nothing special about the TWEAK
method (or the BUILD
method, for that matter). So they can be multi
!
So that brings me to the following solution:
class A {
has %.temps;
multi submethod TWEAK(--> Nil) {
%!temps = a => 42;
}
multi submethod TWEAK(:$test! --> Nil) {
$test
?? (%!temps = a => 666) # testing
!! self.TWEAK(|%_); # NOT testing
}
}
say A.new(:test).temps; # {a => 666}
say A.new(:!test).temps; # {a => 42}
say A.new.temps; # {a => 42}
Note the !
in :$test!
. This makes the named argument mandatory. So that candidate will be selected whenever the test
named argument is specified. But this also includes when it is specified with a False
value, as in :!test
. That's why it needs to be tested for in that candidate.
Also note the %_
in self.TWEAK(|%_)
. All methods in Raku have an implicit *%_
(slurpy hash) parameter defined. So you can use %_
inside a method to indicate all arguments that were not caught by an explicit named parameter (such as :$test
in this case). So self.TWEAK(|%_)
is basically re-dispatching without all explicit named parameters.
Finally: the --> Nil
is just there to indicate that the method will not return a value. This may allow the compiler to produce more efficient bytecode.