syntax role

Documentation for syntax role assembled from the following types:

language documentation Object orientation

From Object orientation

(Object orientation) declarator role role

Roles are a collection of attributes and methods; however, unlike classes, roles are meant for describing only parts of an object's behavior; this is why, in general, roles are intended to be mixed in classes and objects. In general, classes are meant for managing objects and roles are meant for managing behavior and code reuse within objects.

Roles use the keyword role preceding the name of the role that is declared. Roles are mixed in using the does keyword preceding the name of the role that is mixed in.

Roles can also be mixed into a class using is. However, the semantics of is with a role are quite different from those offered by does. With is, a class is punned from the role, and then inherited from. Thus, there is no flattening composition, and none of the safeties which does provides.

constant ⲧ = " " xx 4#Just a ⲧab 
role Notable {
    has Str $.notes is rw;
 
    multi method notes() { "$!notes\n" };
    multi method notesStr $note ) { $!notes ~= "$note\n" ~ ⲧ };
 
}
 
class Journey does Notable {
    has $.origin;
    has $.destination;
    has @.travelers;
 
    method Str { "⤷ $!origin\n" ~ ⲧ ~ self.notes() ~ "$!destination ⤶\n" };
}
 
my $trip = Journey.new:origin<Here>:destination<There>,
                        travelers => <þor Freya> );
 
$trip.notes("First steps");
notes $trip: "Almost there";
print $trip;
# OUTPUT: 
#⤷ Here 
#       First steps 
#       Almost there 
# 
#There ⤶ 

Roles are immutable as soon as the compiler parses the closing curly brace of the role declaration.

Applying roles

Role application differs significantly from class inheritance. When a role is applied to a class, the methods of that role are copied into the class. If multiple roles are applied to the same class, conflicts (e.g. attributes or non-multi methods of the same name) cause a compile-time error, which can be solved by providing a method of the same name in the class.

This is much safer than multiple inheritance, where conflicts are never detected by the compiler, but are instead resolved to the superclass that appears earlier in the method resolution order, which might not be what the programmer wanted.

For example, if you've discovered an efficient method to ride cows, and are trying to market it as a new form of popular transportation, you might have a class Bull, for all the bulls you keep around the house, and a class Automobile, for things that you can drive.

class Bull {
    has Bool $.castrated = False;
    method steer {
        # Turn your bull into a steer 
        $!castrated = True;
        return self;
    }
}
class Automobile {
    has $.direction;
    method steer($!direction{ }
}
class Taurus is Bull is Automobile { }
 
my $t = Taurus.new;
say $t.steer;
# OUTPUT: «Taurus.new(castrated => Bool::True, direction => Any)␤» 

With this setup, your poor customers will find themselves unable to turn their Taurus and you won't be able to make more of your product! In this case, it may have been better to use roles:

role Bull-Like {
    has Bool $.castrated = False;
    method steer {
        # Turn your bull into a steer 
        $!castrated = True;
        return self;
    }
}
role Steerable {
    has Real $.direction;
    method steer(Real $d = 0{
        $!direction += $d;
    }
}
class Taurus does Bull-Like does Steerable { }

This code will die with something like:

===SORRY!===
Method 'steer' must be resolved by class Taurus because it exists in
multiple roles (SteerableBull-Like)

This check will save you a lot of headaches:

class Taurus does Bull-Like does Steerable {
    method steer($direction?{
        self.Steerable::steer($direction)
    }
}

When a role is applied to a second role, the actual application is delayed until the second role is applied to a class, at which point both roles are applied to the class. Thus

role R1 {
    # methods here 
}
role R2 does R1 {
    # methods here 
}
class C does R2 { }

produces the same class C as

role R1 {
    # methods here 
}
role R2 {
    # methods here 
}
class C does R1 does R2 { }

Stubs

When a role contains a stubbed method, a non-stubbed version of a method of the same name must be supplied at the time the role is applied to a class. This allows you to create roles that act as abstract interfaces.

role AbstractSerializable {
    method serialize() { ... }        # literal ... here marks the 
                                      # method as a stub 
}
 
# the following is a compile time error, for example 
#        Method 'serialize' must be implemented by Point because 
#        it's required by a role 
 
class APoint does AbstractSerializable {
    has $.x;
    has $.y;
}
 
# this works: 
class SPoint does AbstractSerializable {
    has $.x;
    has $.y;
    method serialize() { "p($.x$.y)" }
}

The implementation of the stubbed method may also be provided by another role.

Inheritance

Roles cannot inherit from classes, but they may carry classes, causing any class which does that role to inherit from the carried classes. So if you write:

role A is Exception { }
class X::Ouch does A { }
X::Ouch.^parents.say # OUTPUT: «((Exception))␤» 

then X::Ouch will inherit directly from Exception, as we can see above by listing its parents.

As they do not use what can properly be called inheritance, roles are not part of the class hierarchy. Roles are listed with the .^roles metamethod instead, which uses transitive as flag for including all levels or just the first one. Despite this, a class or instance may still be tested with smartmatches or type constraints to see if it does a role.

role F { }
class G does F { }
G.^roles.say;                    # OUTPUT: «((F))␤» 
role Ur {}
role Ar does Ur {}
class Whim does Ar {}Whim.^roles(:!transitive).say;   # OUTPUT: «((Ar))␤» 
say G ~~ F;                      # OUTPUT: «True␤» 
multi a (F $a{ "F".say }
multi a ($a)   { "not F".say }
a(G);                            # OUTPUT: «F␤» 

Pecking order

A method defined directly in a class will always override definitions from applied roles or from inherited classes. If no such definition exists, methods from roles override methods inherited from classes. This happens both when said class was brought in by a role, and also when said class was inherited directly.

role M {
  method f { say "I am in role M" }
}
 
class A {
  method f { say "I am in class A" }
}
 
class B is A does M {
  method f { say "I am in class B" }
}
 
class C is A does M { }
 
B.new.f# OUTPUT «I am in class B␤» 
C.new.f# OUTPUT «I am in role M␤» 

Note that each candidate for a multi-method is its own method. In this case, the above only applies if two such candidates have the same signature. Otherwise, there is no conflict, and the candidate is just added to the multi-method.

Automatic role punning

Any attempt to directly instantiate a role or use it as a type object will automatically create a class with the same name as the role, making it possible to transparently use a role as if it were a class.

role Point {
    has $.x;
    has $.y;
    method abs { sqrt($.x * $.x + $.y * $.y}
    method dimensions { 2 }
}
say Point.new(x => 6=> 8).abs# OUTPUT «10␤» 
say Point.dimensions;              # OUTPUT «2␤» 

We call this automatic creation of classes punning, and the generated class a pun.

Punning is not caused by most metaprogramming constructs, however, as those are sometimes used to work directly with roles.

Parameterized roles

Roles can be parameterized, by giving them a signature in square brackets:

role BinaryTree[::Type{
    has BinaryTree[Type$.left;
    has BinaryTree[Type$.right;
    has Type $.node;
 
    method visit-preorder(&cb{
        cb $.node;
        for $.left$.right -> $branch {
            $branch.visit-preorder(&cbif defined $branch;
        }
    }
    method visit-postorder(&cb{
        for $.left$.right -> $branch {
            $branch.visit-postorder(&cbif defined $branch;
        }
        cb $.node;
    }
    method new-from-list(::?CLASS:U: *@el{
        my $middle-index = @el.elems div 2;
        my @left         = @el[0 .. $middle-index - 1];
        my $middle       = @el[$middle-index];
        my @right        = @el[$middle-index + 1 .. *];
        self.new(
            node    => $middle,
            left    => @left  ?? self.new-from-list(@left)  !! self,
            right   => @right ?? self.new-from-list(@right!! self,
        );
    }
}
 
my $t = BinaryTree[Int].new-from-list(456);
$t.visit-preorder(&say);    # OUTPUT: «5␤4␤6␤» 
$t.visit-postorder(&say);   # OUTPUT: «4␤6␤5␤» 

Here the signature consists only of a type capture, but any signature will do:

enum Severity <debug info warn error critical>;
 
role Logging[$filehandle = $*ERR{
    method log(Severity $sev$message{
        $filehandle.print("[{uc $sev}$message\n");
    }
}
 
Logging[$*OUT].log(debug'here we go'); # OUTPUT: «[DEBUG] here we go␤» 

You can have multiple roles of the same name, but with different signatures; the normal rules of multi dispatch apply for choosing multi candidates.

Mixins of roles

Roles can be mixed into objects. A role's given attributes and methods will be added to the methods and attributes the object already has. Multiple mixins and anonymous roles are supported.

role R { method Str() {'hidden!'} };
my $i = 2 but R;
sub f(\bound){ put bound };
f($i); # OUTPUT: «hidden!␤» 
my @positional := <a b> but R;
say @positional.^name# OUTPUT: «List+{R}␤» 

Note that the object got the role mixed in, not the object's class or the container. Thus, @-sigiled containers will require binding to make the role stick as is shown in the example with @positional. Some operators will return a new value, which effectively strips the mixin from the result. That is why it might be more clear to mix in the role in the declaration of the variable using does:

role R {};
my @positional does R = <a b>;
say @positional.^name# OUTPUT: «Array+{R}␤» 

The operator infix:<but> is narrower than the list constructor. When providing a list of roles to mix in, always use parentheses.

role R1 { method m {} }
role R2 { method n {} }
my $a = 1 but R1,R2# R2 is in sink context, issues a WARNING 
say $a.^name;
# OUTPUT: «Int+{R1}␤» 
my $all-roles = 1 but (R1,R2);
say $all-roles.^name# OUTPUT: «Int+{R1,R2}␤» 

Mixins can be used at any point in your object's life.

# A counter for Table of Contents 
role TOC-Counter {
    has Int @!counters is default(0);
    method Str() { @!counters.join: '.' }
    method inc($level{
        @!counters[$level - 1]++;
        @!counters.splice($level);
        self
    }
}
 
my Num $toc-counter = NaN;     # don't do math with Not A Number 
say $toc-counter;              # OUTPUT: «NaN␤» 
$toc-counter does TOC-Counter# now we mix the role in 
$toc-counter.inc(1).inc(2).inc(2).inc(1).inc(2).inc(2).inc(3).inc(3);
put $toc-counter / 1;          # OUTPUT: «NaN␤» (because that's numerical context) 
put $toc-counter;              # OUTPUT: «2.2.2␤» (put will call TOC-Counter::Str) 

Roles can be anonymous.

my %seen of Int is default(0 but role :: { method Str() {'NULL'} });
say %seen<not-there>;          # OUTPUT: «NULL␤» 
say %seen<not-there>.defined;  # OUTPUT: «True␤» (0 may be False but is well defined) 
say Int.new(%seen<not-there>); # OUTPUT: «0␤»