Perl6: beginning with classes
Perl 6 does support classes within the base language, and most of the syntax has been inherited (no pun intended!) from Moose and alike. One thing to keep in mind when working with classes is that almost every attribute is defined via a twigil, that is a double sigil:
- the first part of the sigil means the type (so the classical $, @, %, &);
- the second part of the sigil defines the visibility, where a
.
(dot) means accessible and a!
means private; - accessing a member does not mean that such member is public, rather that the compiler has defined an accessor method;
- setter accessor are not created by default, but user has to specify them explicitly.
a
, this is the matrix rule-of-thumb for including the member in a class:
first part | second part | twigil | meaning | accessible outside of the instance ? |
$ | . | $.a | a scalar with a getter method named a() | read-only |
$ | ! | $!a | a scalar without any getter or setter | no |
@ | . | @.a | an array with a getter method named a() | read only |
@ | ! | @!a | an array without any getter or setter | no |
% | . | %.a | an hash with a getter named a() | read only |
% | ! | %!a | an hash without any getter or setter | no |
& | . | &.a | a callable method named a() | read only |
& | ! | &!a | a private method named a() | no |
class Person {
has $.name ;
has $.surname ;
has $.age ;
}
my $me = Person.new( name => 'Luca', surname => 'Ferrari', age => 39 );
say $me.surname ~ " " ~ $me.name ~ " aged " ~ $me.age;
$me.surname = 'FERRARI'; # Cannot modify an immutable Str
$me.surname( 'FERRARI' ); # Too many positionals passed; expected 1 argument but got 2
class Person {
has $.name is rw;
has $.surname is rw;
has $.age is rw;
}
my $me = Person.new( name => 'Luca', surname => 'Ferrari', age => 39 );
$me.surname = 'FERRARI';
my $me = Person.new( name => 'Luca', surname => 'Ferrari', age => 39 );
$me.surname() = 'FERRARI';
my $me = Person.new( name => 'Luca', surname => 'Ferrari', age => 39 );
$me.surname( 'FERRARI' );
class Person {
has $.name is rw;
has $.surname is rw;
has $!age; # private!
}
age()
method will result in a no such method exception.
Please note that the age
attribute will not be set by the constructor: a private attribute is not modifiable even at construction time.
This is of course not what we want, and a solution it to place an initialization into the construction chain, that is is
define a BUILD
method (the core behind new
) to handle all the params. The class therefore become:
class Person {
has $.name is rw;
has $.surname is rw;
has $!age;
submethod BUILD( :$!name, :$!surname, :$!age ){ }
method increase_age {
$!age++;
}
method say {
return $!surname ~ " " ~ $!name ~ " aged " ~ $!age;
}
}
$!
twigil, and that is another rule of thumb:
the ! part of a twigil does not exactly means private, but rather accessible only within the class. Since the object has not been
fully initialized when BUILD
is executing, all accessor are not in place, so they cannot be accessed via $.
but rather only
within the class itself.
The same consideration applies to the say
method, that in fact accesses all members via the special $! within class twigil.
The difference between BUILD
and say
is that the former is a so called submethod, that is a special method that is not
inherited by subclasses. This makes them great for object construction and destruction and for all those special tasks that are striclty related
to the type the class is defining. Thanks to the fact that submethods are not going to be substitued by subclasses, they will always be in place
for a specific task (and note that submethods are public!).
For instance the default Mu::new
calls each BUILD
on the inheritance chain, allowing each type on the chain to initialize itself in the proper way without the risk that overriding a build method will lead to inconsistencies even in future (child) classes.
Now, the class can be consumed as follows:
my $me = Person.new( name => 'Luca', surname => 'Ferrari', age => 39 );
$me.increase_age;
say $me.say;
say
method:
method say {
return $!surname ~ " " ~ $!name ~ " aged " ~ $!age;
}
# or ...
method say {
return $.surname ~ " " ~ $.name ~ " aged " ~ $!age;
}
public String say(){
// return $!surname ~ " " ~ $!name ~ " aged " ~ $!age;
return this.surname + " " + this.name + " aged " + this.age;
}
// or ...
public String say(){
// return $.surname ~ " " ~ $.name ~ " aged " ~ $!age;
return this.getSurname() + " " + this.getName() + " aged " + this.age;
}
age
there is no accessor at all, so the only way to access it is using the $! twigil (within the class).
Outside the class, the only accessor available are always named as the member and are by default read-only (i.e., getter), but can be converted to lvalue method (i.e., methods you can assign a value) if the special is rw trait has been applied.
With respect to the BUILD
submethod you can also add some consistency check, for instance preventing to build a person with a negative age:
submethod BUILD( :$!name, :$!surname, :$!age ){
$!age = 0 if ( $!age < 0 );
}
happy_birthday
as follows:
class Person {
has $.name is rw;
has $.surname is rw;
has $!age;
# ... other methods as above
method increase_age {
$!age++;
}
method happy_birthday {
"Happy Birthday $!name !".say;
self.increase_age;
"you are now $!age years old !".say;
}
}
self.increase_age
resolvs to invoking the other method, something similar when in other languages you
do this.increase_age()
. Note also that the twigils are interpolated within strings.
Inheritance
Allow me to quickly expand the example code with a little inheritance, just to show how to use it in Perl 6. Let’s define a baby class, that is the child and extends a person, in the sense it is a “small person”. A baby has all the features of a person and was born from a mother and a father, so something as simple as:class Baby is Person {
has Person $.mother is required;
has Person $.father is required;
}
my $father = Person.new( name => 'John', surname => 'Richardson', age => 30 );
my $mother = Person.new( name => 'Sandra', surname => 'Loreston', age => 30 );
my $baby = Baby.new( name => 'Luca', surname => 'Richardson', age => 2,
mother => $mother,
father => $father );
say $baby.say;
say "Mother: " ~ $baby.mother.say;
say "Father: " ~ $baby.father.say;
Containers
In the above I wrote that accessing a writable member was like using an lvalue method, that is not true in Perl 6. In fact, in Perl 6 each variable (within a class or not) holds a pointer to a container, and the container in turn holds the value of the variable. When you call an accessor to a member, it always returns the container of the member itself. So when you define the member asis rw
what you are truly stating is that the container must be writable, not the member itself.
And this leads to how things work:
$f.name = 'Luca';
name
returns the $!name
container, that is writable, and therefore the assignment puts a new value
into the container. The next time you will call the name
method, the container will be accessed again and the new value will
be presented.
It is something like the following C code pseudo-code:
// ...
char* name(){ return &name; }
// ...
*( f.name() ) = 'LUCA';