Search code examples
raku

How, and how best, to select a method/sub based on a boolean?


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:

  1. How to select the multi method candidate based solely on a boolean's value?

  2. 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 ! * ) { ... }
}

Solution

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