Raku Programming/Classes And Attributes
Classes and Objects
editWhat we've seen so far are the building blocks for procedural programming: Lists of expressions, branches, loops, and subroutines that tell the computer what job to do and exactly how to do it. Raku supports procedural programming very well, but this isn't the only style of programming that Raku supports. Another common paradigm that Raku fits nicely is the object-oriented approach.
Objects are combinations of data and the operations that act on that data. The data of an object are called its attributes, and the operations of an object are called its methods. In this sense, the attributes define the state and the methods define the behavior of the objects.
Classes are templates for creating objects. When referring to an object of a specific class, it's customary to call the object an instance of the class.
Classes
editClasses are defined using the class
keyword, and are given a name:
class MyClass {
}
Inside that class declaration you can define attributes, methods, or submethods.
Attributes
edit
Attributes are defined with the has
keyword, and are specified with a special syntax. For example, let's consider the following class:
class Point3D {
has $!x-axis;
has $!y-axis;
has $!z-axis;
}
The class Point2D defines a point in 3D coordinates with three attributes named x-axis, y-axis and z-axis.
In Raku, all attributes are private and one way to express this explicitly is by using the ! twigil. An attribute declared with the ! twigil can only be accessed directly within the class by using !attribute-name. Another important consequence of declaring attributes this way is that objects cannot be populated by using the default new constructor.
If you declare an attribute with the . twigil instead, a read-only accessor[check spelling] method will be automatically generated. You can think of the . twigil as "attribute + accesor". This accesor, which is a method named after its attribute, can be called from outside the class and return the value of its attribute. In order to allow changes to the attributes through the provided accesors, the trait is rw
must be added to the attributes.
The previous class Point3D could be declared as follows:
class Point3D {
has $.x-axis;
has $.y-axis;
has $.z-axis is rw;
}
Given that the . twigil declares a ! twigil and an accesor[check spelling] method, atrributes can always be used with the ! twigil even if they're declared using the . twigil.
Methods
editMethods are defined just like normal subroutines except for a few key differences:
- Methods use the
method
keyword instead ofsub
. - Methods have the special variable
self
, which refers to the object that the method is being called on. This is known as the invocant. - Methods can access the internal traits of the object directly.
When defining the method, you can specify a different name for the invocant, instead of having to use self
. To do this, you put it at the beginning of the method's signature and separate it from the rest of the signature with a colon:
method myMethod($invocant: $x, $y)
In this context, the colon is treated like a special type of comma, so you can write it with additional whitespace if that is easier:
method myMethod($invocant : $x, $y)
Here's an example:
class Point3D {
has $.x-axis;
has $.y-axis;
has $.z-axis;
method set-coord($new-x, $new-y, $new-z) {
$!x-axis = $new-x;
$!y-axis = $new-y;
$!z-axis = $new-z;
}
method print-point {
say "("~$!x-axis~","~$!y-axis~","~$!z-axis~")";
}
# method using the self invocant
method distance-to-center {
return sqrt(self.x-axis ** 2 + self.y-axis ** 2);
}
# method using a custom invocant named $rect
method polar-coordinates($rect:) {
my $r = $rect.distance-to-center;
my $theta = atan2($rect.y-axis, $rect.x-axis);
return "("~$r~","~$theta~","~$rect.z-axis~")";
}
}
Objects
editObjects are data items whose type is a given class. Objects contain any attributes that the class defines, and also has access to any methods in the class. Objects are created with the new
keyword.
Using the class Point3D:
my $point01 = Point3D.new();
The class constructor, new()
can take named methods used to initialize any of the class attributes:
# Either syntax would work for object initialization
my $point01 = Point3D.new(:x-axis(3), :y-axis(4), :z-axis(6));
my $point02 = Point3D.new(x-axis => 3, y-axis => 4, z-axis => 6);
Methods from that class are called using the dot notation. This is the object, a period, and the name of the method after that.
$point01.print-point();
say $point01.polar-coordinates();
When an object isn't provided for dot notation method calls, the default variable $_
is used instead:
$_ = Point3D.new(:x-axis(6), :y-axis(8), :z-axis(6));;
.print-point();
say .polar-coordinates();
Inheritance
editBasic class systems enable data and the code routines that operate on that data to be bundled together in a logical way. However, there are more advanced features of most class systems that enable inheritance too, which allows classes to build upon one another. Inheritance is the ability for classes to form logical hierarchies. Raku supports normal inheritance of classes and subclasses, but also supports special advanced features called mixins, and roles. We are going to have to reserve some of the more difficult of these features for later chapters, but we will introduce some of the basics here.
Basic Types
editWe've talked about a few of Perl's basic types in earlier chapters. It may surprise you to know that all Raku data types are classes, and that all these values have built-in methods that can be used. Here we're going to talk about some of the methods that can be called on some of the various objects that we've seen so far.
.print
and .say
edit
We've already seen the print
and say
builtin functions. All built-in classes have methods of the same name that print a stringified form of the object.
.perl
and eval
edit
We're going to take a quick digression and talk about the eval
function. eval
lets us compile and execute a string of Raku code at runtime.
eval("say 'hello world!';");
All Raku objects have a method called .perl
that returns a string of Raku code representing that object.
my Int $x = 5;
$x.perl.say; # "5"
my @y = (1, 2, 3);
@y.perl.say; # "[1, 2, 3]"
my %z = :first(1), :second(2), :third(3);
%z.perl.say; # "{:first(1), :second(2), :third(3)}"
Context and Coercion methods
editThere are a number of methods that can be called to explicitly change the given data item into a different form. This is like an explicit way to force the given data item to be taken in a different context. Here is a partial list:
.item
- Returns the item in scalar context.
.iterator
- Returns an iterator for the object. We'll talk about iterators in a later chapter.
.hash
- Returns the object in hash context
.list
- Returns the object in array or "list" context
.Bool
- Returns the boolean value of the object
.Array
- Returns an array containing the object data
.Hash
- Returns a hash containing the object data
.Iterator
- Returns an iterator for the object. We'll talk about iterators in a later chapter.
.Scalar
- Returns a scalar reference to the object
.Str
- Returns a string representation for the object
Introspection Methods
edit.WHENCE
- Returns a code reference for the object types autovivification closure. We'll talk about autovivification and closures later.
.WHERE
- Returns the memory location address of the data object
.WHICH
- Returns the objects identity value, which for most objects is just it's memory location (it's .WHERE)
.HOW
- (HOW = Higher Order Workings) Returns the meta class which handles this object
.WHAT
- Returns the type object of the current object