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 thenewmethod).If no
BUILDmethod 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';TWEAKis called should it exist. It will receive the same argumentsBUILDreceives.
This object construction scheme has several implications:
Named arguments to the default
newconstructor (inherited fromMu) can correspond directly to public attributes of any of the classes in the method resolution order, or to any named parameter of anyBUILDorTWEAKsubmethod.Custom
BUILDmethods 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 ownBUILDmethod.BUILDmay 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.TWEAKon 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
BUILDmethod:class Point { has $.x; has $.y; submethod BUILD(:$x, :$y) { $!x := $x; $!y := $y; } } my $p1 = Point.new( x => 10, y => 5 );
The following
BUILDmethod 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
Inttype. Ifnewis called without this parameter, then a default ofAnywill be assigned, which will cause a type error. The easy fix is to add a default value to theBUILDparameter.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)»
BUILDallows 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
newis not special in Raku. It is merely a common convention, one that is followed quite thoroughly in most Raku classes. You can callblessfrom any method, or useCREATEto fiddle around with low-level workings.If you want a constructor that accepts positional arguments, you must write your own
newmethod:class Point { has $.x; has $.y; method new($x, $y) { self.bless(:$x, :$y); } }
Do note, however, that
newis a normal method and not involved in any of the construction process ofbless. So any logic placed in thenewmethod will not be called when using a differentnewmethod or anewof 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.