📘 Constructors in Perl 6 classes

You may have noticed in the previous examples that two different approaches to creating a typed variable were used.

The first was via an explicit call of the new constructor. In this case, a new instance was created.

my $a = A.new;

In the second, a variable was declared as a typed variable. Here, a container was created.

my A $a;

Creating a container means not only that the variable will be allowed to host an object of that class but also that you will still need to create that object itself.

my A $a = A.new;

Let us consider an example of a class which involves one public method and one public data field.

class A {
    has $.x = 42;
    method m {
        say "A.m";
    }
}

The internal public variable $.x is initialized with the constant value.

Now, let us create a scalar container for the variable of the A class.

my A $a;

The container is here, and we know its type, but there are no data yet. At this moment, the class method may be called. It will work, as it is a class method and does not require any instance with real data.

$a.m; # Prints “A.m”

Meanwhile, the $.x field is not available yet.

say $a.x; # Error: Cannot look up attributes in a A type object

We need to create an instance object by calling a constructor first.

my A $b = A.new;
say $b.x; # Prints 42

Please note that the initialization (= 42) only happens when a constructor is called. Prior to this, there is no object, and thus no value can be assigned to an attribute.

The new method is inherited from the Mu class. It accepts a list of the named arguments. So, this method can be used on any object with any reasonable arguments. For instance:

my A $c = A.new(x => 14);
say $c.x; # 14, not 42

Note that the name of the field (x) may not be quoted. An attempt of A.new(‘x’ => 14) will fail because it will be interpreted as a Pair being passed as a positional parameter.

Alternatively, you can use the :named(value) format for specifying named parameters:

my A $c = A.new :x(14); # Or A.new(:x(14)) if you wish
say $c.x; # 14

For the more sophisticated constructors, the class’s own BUILD submethod may be defined. This method expects to get a list of the named arguments.

class A {
    # Two fields in an object.
    # One of them will be calculated in the constructor.
    has $.str;
    has $!len;

    # The constructor expects its argument named ‘str’.
    submethod BUILD(:$str) {
        # This field is being copied as is:
        $!str = $str;

        # And this field is calculated:
        $!len = $str.chars;
    }

    method dump {
        # Here, we print the current values.
        # The variables are interpolated as usual
        # but to escape an apostrophe character from
        # the variable name, a pair of braces is added.
        "{$.str}'s length is $!len.".say;
    }
}

my $a = A.new(str => "Perl");
$a.dump;

This programme prints the following output:

Perl’s length is 4.

📘 Roles in Perl 6

Apart from the bare classes, the Perl 6 language allows roles. These are what are sometimes called interfaces in other object-oriented languages. Both the methods and the data, which are defined in a role, are available for “addition” (or mixing-in) to a new class with the help of the does keyword.

A role looks like a base class that appends its methods and data to generate a new type. The difference between prepending a role and deriving a class from a base class is that with a role, you do not create any inheritance. Instead, all the fields from the role become the fields of an existing class. In other words, classes are the is a characteristic of an object, while roles are the does traits. With roles, name conflicts will be found at compile time; there is no need to traverse the method resolution order paths.

The following example defines a role, which is later used to create two classes; we could achieve the same with bare inheritance, though:

# The role of the catering place is to take orders 
# (via the order method), to count the total amount 
# of the order (method calc) and issuing a bill (method bill).

role FoodService {
    has @!orders;

    method order($price) {
        @!orders.push($price);
    }

    method calc {
        # [+] is a hyperoperator (hyperop) connecting all the
        # elements of an array.
        # It means that [+] @a is equivalent to
        # @a[0] + @a[1] + ... + @a[N].
        return [+] @!orders;
    }

    method bill {
        # Order's total is still a sum of the orders.
        return self.calc;
    }
}

# Launching a cafe. A cafe is a catering place.
class Cafe does FoodService {
    method bill {
        # But with a small surcharge.
        return self.calc * 1.1;
    }
}

# And now a restaurant.
class Restaurant does FoodService {
    method bill {
        # First, let the customer wait some time.
        sleep 10.rand;

        # Second, increase the prices even more.
        return self.calc * 1.3;
    }
}

Let us try that in action. First, the cafe.

my $cafe = Cafe.new;
$cafe.order(10);
$cafe.order(20);
say $cafe.bill; # Immediate 33

Then, the restaurant. (Note that this code will have a delay because of the class definition).

my $restaurant = Restaurant.new;
$restaurant.order(100);
$restaurant.order(200);
say $restaurant.bill; # 390 after some unpredictable delay

Roles can be used for defining and API and forcing the presence of a method in a class that uses a role. For example, let’s create a role named Liquid, which requires that the flows method must be implemented.

role Liquid {
    method flows {...}
}

class Water does Liquid {
}

It is not possible to run this programme as it generates a compile-time error:

Method 'flows' must be implemented by Water because it is required by a role

Note that the ellipsis … is a valid Perl 6 construction that is used to create forward declarations.