Home > Enterprise >  How to initialize attributes inherited in a sub-class?
How to initialize attributes inherited in a sub-class?

Time:07-23

use v6.d;

class Foo {
    has $.name;

    submethod BUILD (:$!name = 'John') {};
}
my $f = Foo.new;
say $f;
# OUTPUT: Foo.new(name => "John")

That works as expected. When I add:

class Bar is Foo {
    submethod BUILD (:$!name = 'Jane') {};
}
my $b = Bar.new;
say $b;

===SORRY!=== Error while compiling scratch.raku
Attribute $!name not declared in class Bar
at scratch.raku:14

How can I assign default values at construction time?

Thank you, Jim

CodePudding user response:

Here's an approach using TWEAK adapted from brian d foy's book, "Learning Raku" (formerly "Learning Perl6"), Chapter 12: "Classes":

use v6.d;

class Foo {
    has $!default-name = 'John';
    has $.custom-name  is rw;

    submethod TWEAK (:$custom-name) {
      self.custom-name = $custom-name // $!default-name;
      };
}

my $f = Foo.new;
say $f; 
put "This is class Foo with {$f.custom-name} as name.";


class Bar is Foo {}

my $b = Bar.new;
$b.custom-name = 'Jane';
say $b;
put "This is class Bar with {$b.custom-name} as name.";

Output:

Foo.new(custom-name => "John")
This is class Foo with John as name.
Bar.new(custom-name => "Jane")
This is class Bar with Jane as name.

Above class Foo's "John" takes the default-name when no custom-name is assigned. In the second example class Bar's "Jane" takes the custom name assigned to it.

You can modify class Bar to have its own default-name, and its own TWEAK submethod. Then the only difference between the original class Foo and the inherited class Bar is Foo {} appears to be declaring $.default-name as a public method.

https://www.learningraku.com/2018/08/14/table-of-contents/
https://www.oreilly.com/library/view/learning-perl-6/9781491977675/

CodePudding user response:

Here is an edited version based on your comment. This way the default for the child class is set in the child class, but may be explicitly set if needed.

use v6.d;

class Foo {
    has $.name = 'John'
}

class Bar is Foo {
    method new( *%h ) { 
        %h<name> //= 'Jane';
        nextwith( |%h )
    }   
}

Foo.new.say;                     # OUTPUT: Foo.new(name => "John")
Bar.new.say;                     # OUTPUT: Bar.new(name => "Jane")
Bar.new(name => 'Dora').say;     # OUTPUT: Bar.new(name => "Dora")

Since my previous version relied on TWEAK, I thought it would be fun to try that way too.

class Foo {
    has $!name;

    multi method name {              #getter
        $!name 
    }   
    multi method name( $value ) {    #setter
        $!name = $value 
    }   

    submethod TWEAK {
        $!name //= 'John'
    }   

    method gist(::T:) {              #captures type of its invocant
        "{::T.^name}.new(name => \"$!name\")"
    }   
}

class Bar is Foo {
    submethod TWEAK( :$name ) { 
        self.name: $name // 'Jane'
    }   
}

Foo.new.say;                     # OUTPUT: Foo.new(name => "John")
Bar.new.say;                     # OUTPUT: Bar.new(name => "Jane")
Bar.new(name => 'Dora').say;     # OUTPUT: Bar.new(name => "Dora")

This is a bit trickier since the public attribute shorthand has $.name; auto-generates (i) public accessor getter/setter methods with (ii) a proxy for easy assignment and (iii) adjusts the .gist for a quick and easy view of all public attrs via .say. BUT these features are not ready until construction is done.

Therefore this example has to have explicit setter/getter multi methods with has $!name; as private attribute.

The public attribute shorthand is like training wheels for OO that provides easy use of basic OO as transparent data structures with a low learning curve (like in Python). Sidestepping this is like putting raku into more formal OO mode with proper encapsulation. As you climb the OO hill, with inheritance, roles, delegation, trust and so on, raku gently encourages you to get more formal.

Here we need to do this to get a way to access the $!name attribute in the parent class during construction.

It's easy enough now for Foo's TWEAK submethod to apply the default by assigning to it's private $!name.

Now Bar's TWEAK submethod can use the getter/setter since all regular methods are available at TWEAK time. (Actually you get the same outcome if you use BUILD instead of TWEAK in this example.)

A couple of other adjustments -

  • say calls .gist on an object - so we need to explicitly put private attributes back in with a custom .gist if you want to show them
  • here I use a ::T type capture so that I only have to do that once and the same parent method works for all child classes
  • need to use self. instead of $. at TWEAK time (all that $. virtual method machinery comes in afterwards), and
  • need to use the method syntax for setter methods self.name: 'value' (which IMO is a nicer way to go self.name('value')) since the public attr proxy is not there

The following is a bit out of context now after this edit, but I will leave here anyway.

The magic is that special methods new, BUILD and TWEAK will auto set named attrs based on their signature alone.

Here's why I prefer to use TWEAK instead of BUILD:

BUILD may set an attribute, but it does not have access to the contents of the attribute declared as its default as they are only applied later. TWEAK on the other hand is called after default values have been applied and will thus find the attributes initialized. So it can be used to check things or modify attributes after object construction.

  • Related