Classes are defined with the class
keyword. By default, all attributes
are private attributes (meaning they're only accessible within the class they're defined)
and are declared with the has
keyword and the !
twigil. Declaring an attribute
with the .
twigil declares a public attribute (also known as a private
attribute with a read-only accessor method named after the attribute).
With each new class, Raku automatically provides a new
constructor which
takes named arguments and uses them to initialize the public attributes.
class Point2D {
has Str $!id = 'P2'; # private attr. with default value
has Numeric $.x; # attr. with read-only accesor
has Numeric $.y; # same as above
}
# Initializing an object instance. Private attributes
# cannot be initialized during object construction.
my $p1 = Point2D.new(x => 3, y => 4);
say "x: ", $p1.x; # invoking the read-only accessor
say "y: ", $p1.y; # same as above
# An attribute with a read-only accesor cannot be modified
# from outside the class. To do so, add the `is rw` trait
# to make it read-write. For example: `has Numeric $.x is rw;`
$ perl6 class.p6
x: 3
y: 4
Methods are declared with the method
keyword. By default, all methods are
public. To make a method private, prepend a !
to the method's name. Similar
to subroutines, methods can have typed parameters, typed returns, be multi
,
etc.
class Point2D {
has Str $!id;
has Numeric $.x;
has Numeric $.y;
method rect-coordinates {
return (self.x, self.y); # Or: ($.x, $.y)
}
# notice the ! before the method's name
method !distance-to-center {
# public attr. can be accessed with the ! twigil
return sqrt($!x ** 2 + $!y ** 2);
}
method polar-coordinates {
# calling a method is done on `self` or an explicit invocant.
# use `!` for a private attr. and a `.` for a public attr.
my $radius = self!distance-to-center; # calling a private method
my $theta = atan2($!y, $!x);
return $radius, $theta;
}
}
my $p2 = Point2D.new(:x(3), :y(4)); # using the colon-pair syntax
say "Cartesian coordinates: ", $p2.rect-coordinates;
say "Polar coordinates: ", $p2.polar-coordinates;
$ perl6 class.p6
Cartesian coordinates: (3 4)
Polar coordinates: (5 0.9272952180016122)
A method's signature can have an explicit invocant as its first parameter
which is created by placing a colon after it. This allows for the method to
refer to the object it was called on explicitly. Furthermore, the ::?CLASS
variable can be used, combined with either :U
(for class methods) and :D
(for instance methods).
class Point2D {
has Str $!id;
has Numeric $.x;
has Numeric $.y;
# this is a class method. it's an alternate
# constructor method, only callable on the class.
method create-point(::?CLASS:U $POINT: $abs, $ord) {
#^^^^^ Explicit invocant
$POINT.new(x => $abs, y => $ord);
}
# Another construct but instead of using `new` as intermediary,
# we're calling the `bless` method which `new` calls anyway.
method point(::?CLASS:U: $abs, $ord ) {
# `bless` must always receive named arguments.
self.bless( x => $abs, y => $ord);
}
method rect-coordinates {
return (self.x, self.y);
}
method !distance-to-center {
return sqrt($!x ** 2 + $!y ** 2);
}
method polar-coordinates {
my $radius = self!distance-to-center;
my $theta = atan2 $!y, $!x;
return $radius, $theta;
}
}
my $p3 = Point2D.create-point(8, 12);
my $p3_a = Point2D.point(-2, 3);
say "Cartesian coordinates: ", $p3.coordinates;
say "Cartesian coordinates: ", $p3_a.coordinates;
$ perl6 class.p6
Cartesian coordinates: (8 12)
Cartesian coordinates: (-2 3)
Submethods are public methods that are not inherited by subclasses. Two notable examples of submethods are
BUILD
- useful for object construction and destruction tasks.TWEAK
- ideal for checking things or modify attributes after object
construction.class Point2D {
has Str $!id;
has Numeric $.x;
has Numeric $.y;
submethod TWEAK {
# assign an id after object construction
$!id = ('A'..'Z', 0..9).flat.roll(6).join ~ 'P2';
}
method get-id {
return $!id;
}
method create-point(::?CLASS:U $POINT: $abs, $ord) {
$POINT.new(x => $abs, y => $ord);
}
method point(::?CLASS:U: $abs, $ord ) {
self.bless( x => $abs, y => $ord);
}
method rect-coordinates {
return (self.x, self.y);
}
method !distance-to-center {
return sqrt($!x ** 2 + $!y ** 2);
}
method polar-coordinates {
my $radius = self!distance-to-center;
my $theta = atan2 $!y, $!x;
return $radius, $theta;
}
}
my $p3_c = Point2D.create-point(-5, 2);
say "Point at {$p3_c.rect-coordinates} has ID: {$p3_c.get-id}";
$ perl6 class.p6
Point at -5 2 has ID: O76M9CP2
Inheritance is achieved in Raku by using the is
keyword.
sub MovablePoint is Point2D {
has Numeric $.x is rw; # child's attributes are
has Numeric $.y is rw; # now read-write
# translate both vertically and horizontally
multi method translate( Numeric $abs, Numeric $ord ) {
$!x += $abs;
$!y += $ord;
Nil;
}
# translate horizontally
multi method translate( Numeric $abs ) {
$!x += $abs;
Nil;
}
}
my $p4 = MovablePoint.new(:x(12), :y(-5));
say "Coordinates: ", $p4.rect-coordinates;
say "Polar coordinates: ", $p4.polar-coordinates;
say "---> Translating point...";
$p4.translate(5, 6);
# or: `$p4.x = 5; $p4.y = 6;` since
# accessors are now read-write
say "New coordinates: ", $p4.rect-coordinates;
say "New polar coordinates: ", $p4.polar-coordinates;
$ perl6 class.p6
Coordinates: (12 -5)
Polar coordinates: (13 -0.3947911196997615)
---> Translating point...
New coordinates: (17 1)
New polar coordinates: (13 -0.3947911196997615)
While the rectangular coordinates changed when the point moved, the
polar coordinates didn't change due to the way the values of x
and
y
are accessed in the parent class Point2D
from which MovablePoint
inherits. In the rect-coordinates
method, x
and y
are accessed
through their accessor methods (self.x
and self.y
) while in the
polar-coordinates
they're accessed directly ($!x
and $!y
). Keep
this in mind if you don't plan on overriding the inherited methods
in the child class.
A role is defined with the role
keyword and applied with does
.
role Colored {
has %.color;
multi method change-color( Int $red, Int $green, Int $blue ) {
given self {
.color<red> = $red;
.color<green> = $green;
.color<blue> = $blue;
}
Nil;
}
multi method change-color( $point: %hue ) {
given $point {
.color<red> = %hue<red> // 0;
.color<green> = %hue<green> // 0;
.color<blue> = %hue<blue> // 0;
}
Nil;
}
}
# Application of a role during object initialization:
my $p5 = Point2D.new(:x(9), :y(18)) does Colored;
say "Color: ", $p5.color ?? $p5.color !! "Not Yet";
$p5.change-color(220, 25, 78);
say "New Color: ", $p5.color;
$p5.change-color({red => 10, green => 50, blue => 120});
say "Another color: ", $p5.color;
# Application of a role to a class:
class ColoredPoint is Point2D does Colored {
}
my $p6 = ColoredPoint.new(
:x(9),
:y(18),
color => { :red(45), :green(56), :blue(200) }
);
say "Pt. 6's color: ", $p6.color;
say $p6;
$ perl6 class.p6
Color: Not Yet
New Color: {blue => 78, green => 25, red => 220}
Another color: {blue => 120, green => 50, red => 10}
Pt. 6's color: {blue => 200, green => 56, red => 45}
ColoredPoint.new(color => {:blue(200), :green(56), :red(45)}, x => 9, y => 18)
Each user-defined class has a gist
method that provides a default
representation of the class. If a different representation is needed,
this method can be overriden to prepare the output as desired.
# Let's use the `ColoredPoint` class. Both `Point2D` and `Colored` are
# implied to be present here.
class ColoredPoint is Point2D does Colored {
method gist {
qq:to/EOF/;
πΊοΈ ($.x, $.y)
π΄ %!color<red>
π %!color<green>
π΅ %!color<blue>
EOF
}
}
my $colored_point = ColoredPoint.new(
:x(9),
:y(18),
color => { :red(45), :green(56), :blue(200) }
);
say $colored_point; # printing out the object!
$ perl6 class.p6
πΊοΈ (9, 18)
π΄ 45
π 56
π΅ 200
Introspection is the process of gathering information about some objects in a program.
Given an object, some questions can be asked. Few of them are:
~~
- smartmatch the object against a class. If the object is of that
class, or of an inheriting class, it returns True
.
.gist
- returns a default and terse representation of an object, though
it can be overriden to provide the desired representation. The sub (or method)
say
calls this method by default.
.perl
- returns a string that can be executed as Perl code, and
reproduces the original object.
.^name
- calls the name
method on the meta object and returns the class
name.
.attributes
- calls the attributes
method on the meta object and returns
all attributes of the object, although providing the :local
named argument
limits the returned attributes to those defined in the class.
.^methods
- calls the methods
method on the meta object and returns
a list of the methods callable on the object. Using the :local
named argument
limits the returned methods to those defined in the class.
.^parents
- calls the parents
method on the meta object and returns
the parent classes of the object's class.
The syntax of calling a method with .^
instead of just .
means that
it is actually a method call on its meta class, which is a class managing
the properties any given class. To learn more about, head over to
https://docs.perl6.org/language/mop.
my $obj = ColoredPoint.new(
:x(9),
:y(18),
color => { :red(45), :green(56), :blue(200) }
);
if $obj ~~ ColoredPoint {
say "It's a colored point."
};
if $obj ~~ Point2D {
say "It's also a point since it inherits from Point2D."
};
say $obj.gist;
say $obj.perl;
say $obj.^name; # name of the object's class
say $obj.^attributes; # all attributes including parent class'
say $obj.^attributes(:local); # only in-class defined attributes
say $obj.^methods; # all methods including parent class'
say $obj.^methods(:local); # only methods defined in the class
say $obj.^parents; # parent class(es)
$ perl6 introspect.p6
It's a colored point.
It's also a point since it inherits from Point2D.
πΊοΈ (9, 18)
π΄ 45
π 56
π΅ 200
ColoredPoint.new(color => {:blue(200), :green(56), :red(45)}, x => 9, y => 18)
ColoredPoint
(Associative %!color Str $!id Numeric $!x Numeric $!y)
(Associative %!color)
(gist change-color color BUILDALL x rect-coordinates get-id y polar-coordinates point create-point BUILDALL TWEAK)
(gist change-color color BUILDALL)
((Point2D))
For more info, go to