Raku - default member values

TL;DR

An example of a Raku class with default values for members.

This post is probably trivial for most, but I guess Iā€™ll come back looking at it several times. Hi future Flavio!

I wanted to know how to handle default values for member variables in a class. It turns out that itā€™s managed the way I was expecting it - i.e. using signatures to specify default values.

Thereā€™s still some ā€œcargo cultā€ I have regarding to when I have to use $!member and when $.member, but I trust Iā€™ll get the hang of it.

Hereā€™s the example:

class DefaultedMember {
   has $!member;
   has $!other-member;
   has &!callback;
   has @!items;
   submethod BUILD (
      :$!member = 'whatever',
      :&!callback = { '[' ~ $^a ~ ']' },
      :$!other-member,
      :@some-items,
   ) {
      $!member = 'fixed-prefix-' ~ $!member if $!member ~~ /hello/;
      $!other-member //= 'hey!';
      @!items = $!member;
      self.add-to-items(@some-items);
   }
   method add-to-items (*@new-items) {
      @!items.push: @new-items.Slip;
   }
   method talk {
      put &!callback($!member), ' ', $!other-member, ' ', @!items.gist;
   }
}

All member variables are private (declared with the ! twigil). Our goal is to make sure all of them have the right value when the object is initialized.

For my purposes, the BUILD method/submethod proved sufficient. I still donā€™t know when I should use one or the other, but I hope Iā€™ll get the hang of it shortly. Other alternatives, as I understand, are the TWEAK method/submethod and a brand new new method, which is the last resort for complex things (I guess).

The most straightforward way of providing a default value is to put it directly in the signature for BUILD. My understanding is that naming the variable in the signature the same as the member variable (and prefixing it with :) makes BUILD initialize the member itself as we expect. This happens before we enter the code block associated to BUILD. Another way is to use the code block itself.

Letā€™s see what happens for the different members:

  • $!member is initialized from the signature, so entering the block it either has the value passed in from the invocant, or the default value whatever. It is further conditionally modified inside the block, so for example if the input value is the string hello, world!, its value becomes prefix-hello, world!.
  • &!callback is initialized from the signature only, getting either the value passed by the invocant, or the default code block provided directly in the signature;
  • $!other-member is initialized by the signature, getting either the value passed from the invocant, or an Any value (I guess). For this reason, we then initialize it inside the block in the case that the value is not defined. This shows that we can do complex initialization of a variable when its value is not passed in;
  • @!items cannot be initialized through the signature, but its value is computed based on other member variables ($!member and $!other-member) as well as an additional, optional input parameter @some-items, which are set by invoking method add-to-items. This shows that:
    • itā€™s possible to have a constructor signature that does not necessarily need to reflect the internal structure of the class, which is much appreciated;
    • itā€™s possible to call other methods from BUILD, which is much appreciated as well!

Hereā€™s an example sequence of invocations, as well as their result:

DefaultedMember.new.talk;
# OUTPUT: ļ½¢[whatever] hey! [whatever]ā¤ļ½£


DefaultedMember.new(member => 'hello, world!').talk;
# OUTPUT: ļ½¢[prefix-hello, world!] hey! [prefix-hello, world!]ā¤ļ½£


DefaultedMember.new(
   member => 'hi there!',
   callback => { 'Ā«' ~ $^input ~ 'Ā»' },
).talk;
# OUTPUT: ļ½¢Ā«hi there!Ā» hey! [hi there!]ā¤ļ½£


DefaultedMember.new(
   member => 'hi there!',
   callback => { 'Ā«' ~ $^input ~ 'Ā»' },
   other-member => "I'm here too!",
).talk;
# OUTPUT: ļ½¢Ā«hi there!Ā» I'm here too! [hi there!]ā¤ļ½£


DefaultedMember.new(
   member => 'hi there!',
   callback => { 'Ā«' ~ $^input ~ 'Ā»' },
   other-member => "I'm here too!",
   some-items => < and here we go >,
).talk;
# OUTPUT: ļ½¢Ā«hi there!Ā» I'm here too! [hi there! and here we go]ā¤ļ½£

Now Iā€™m left with a few doubts:

  • should I declare BUILD as a method or as a submethod? Iā€™ve seen both in several examples, and Iā€™m not sure when I need either one;
  • should I even use BUILD to do this, or should I use TWEAK?
  • When do I need to completely override new? (I have an idea regarding thisā€¦)

If you made it so far, and know the answer, and have 5 minutes to spareā€¦ ring a bell in the comments or by email to flavio @t polettix.it!


Comments? Octodon, , GitHub, Reddit, or drop me a line!