In Object orientation§

See primary documentation in context for Object construction.

Objects are generally created through method calls, either on the type object or on another object of the same type.

Class Mu provides a constructor method called new, which takes named arguments and uses them to initialize public attributes.

class Point {
    has $.x;
    has $.y;
}
my $p = Point.new( x => 5, y => 2);
#             ^^^ inherited from class Mu
say "x: ", $p.x;
say "y: ", $p.y;
# OUTPUT: «x: 5␤»
# OUTPUT: «y: 2␤»

Mu.new calls method bless on its invocant, passing all the named arguments. bless creates the new object, and then walks all subclasses in reverse method resolution order (i.e. from Mu to most derived classes). In each class bless executes the following steps in the order given here:

  • It checks for the existence of a method named BUILD. If the method exists, the method is called with all the named arguments it received (from the new method).

  • If no BUILD method was found, the public attributes from this class are initialized from named arguments of the same name.

  • All attributes that have not been touched in any of the previous steps have their default values applied:

    has $.attribute = 'default value';

  • TWEAK is called should it exist. It will receive the same arguments BUILD receives.

This object construction scheme has several implications:

  • Named arguments to the default new constructor (inherited from Mu) can correspond directly to public attributes of any of the classes in the method resolution order, or to any named parameter of any BUILD or TWEAK submethod.

  • Custom BUILD methods should always be submethods, otherwise they are inherited to subclasses and prevent default attribute initialization (item two in the above list) should the subclass not have its own BUILD method.

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

    class RectangleWithCachedArea {
        has ($.x1, $.x2, $.y1, $.y2);
        has $.area;
        submethod TWEAK() {
            $!area = abs( ($!x2 - $!x1) * ( $!y2 - $!y1) );
        }
    }
    
    say RectangleWithCachedArea.new( x2 => 5, x1 => 1, y2 => 1, y1 => 0).area;
    # OUTPUT: «4␤»
    
  • Since passing arguments to a routine binds the arguments to the parameters, one can simplify BUILD methods by using the attribute as a parameter.

    A class using ordinary binding in the BUILD method:

    class Point {
        has $.x;
        has $.y;
    
        submethod BUILD(:$x, :$y) {
            $!x := $x;
            $!y := $y;
        }
    }
    my $p1 = Point.new( x => 10, y => 5 );
    

    The following BUILD method is equivalent to the above:

    submethod BUILD(:$!x, :$!y) {
        # Nothing to do here anymore, the signature binding
        # does all the work for us.
    }
    
  • In order to use default values together with a `BUILD()` method one can't use parameter binding of attributes, as that will always touch the attribute and thus prevent the automatic assignment of default values (step three in the above list). Instead one would need to conditionally assign the value:

    class A {
        has $.attr = 'default';
        submethod BUILD(:$attr) {
            $!attr = $attr if defined $attr;
        }
    }
    say A.new(attr => 'passed').raku;
    say A.new().raku;
    # OUTPUT: «A.new(attr => "passed")␤»
    # OUTPUT: «A.new(attr => "default")␤»
    

    It's simpler to set a default value of the `BUILD` parameter instead though:

    class A {
        has $.attr;
        submethod BUILD(:$!attr = 'default') {}
    }
    
  • Be careful when using parameter binding of attributes when the attribute has a special type requirement such as an Int type. If new is called without this parameter, then a default of Any will be assigned, which will cause a type error. The easy fix is to add a default value to the BUILD parameter.

    class A {
        has Int $.attr;
        submethod BUILD(:$!attr = 0) {}
    }
    say A.new(attr => 1).raku;
    say A.new().raku;
    # OUTPUT: «A.new(attr => 1)␤»
    # OUTPUT: «A.new(attr => 0)␤»
    
  • BUILD allows to create aliases for attribute initialization:

    class EncodedBuffer {
        has $.enc;
        has $.data;
    
        submethod BUILD(:encoding(:$!enc), :$!data) { }
    }
    my $b1 = EncodedBuffer.new( encoding => 'UTF-8', data => [64, 65] );
    my $b2 = EncodedBuffer.new( enc      => 'UTF-8', data => [64, 65] );
    #  both enc and encoding are allowed now
    
  • Note that the name new is not special in Raku. It is merely a common convention, one that is followed quite thoroughly in most Raku classes. You can call bless from any method, or use CREATE to fiddle around with low-level workings.

  • If you want a constructor that accepts positional arguments, you must write your own new method:

    class Point {
        has $.x;
        has $.y;
        method new($x, $y) {
            self.bless(:$x, :$y);
        }
    }
    

    Do note, however, that new is a normal method and not involved in any of the construction process of bless. So any logic placed in the new method will not be called when using a different new method or a new of a subclass.

    class Vector {
        has $.x;
        has $.y;
        has $.length;
        method new($x, $y) {
            self.bless(:$x, :$y, length => sqrt($x**2 * $y**2));
        }
    }
    
    class NamedVector is Vector {
        has $.name;
        method new($name, $x, $y) {
            self.bless(:$name, :$x, :$y);
        }
    }
    
    my $v = Vector.new: 3, 4;
    say $v.length; # OUTPUT: «5␤»
    
    my $f = NamedVector.new: 'Francis', 5, 12;
    say $f.length; # OUTPUT: «(Any)␤»
    

Here is an example where we enrich the Str class with an auto-incrementing ID:

class Str-with-ID is Str {
    my $counter = 0;
    has Int $.ID  is rw = 0;

    multi method new( $str ) {
        self.bless( value => $str );
    }
    submethod BUILD( :$!ID = $counter++ ) {}
}

say Str-with-ID.new("1.1,2e2").ID;                  # OUTPUT: «0␤»
my $enriched-str = Str-with-ID.new("3,4");
say "$enriched-str, {$enriched-str.^name}, {$enriched-str.ID}";
# OUTPUT: «3,4, Str-with-ID, 1␤»

We create a custom new since we want to be able to be able to initialize our new class with a bare string. bless will call Str.BUILD which will capture the value it's looking for, the pair value => $str and initialize itself. But we have to also initialize the properties of the subclass, which is why within BUILD we initialize $.ID. As seen in the output, the objects will be correctly initialized with an ID and can be used just like a normal Str.