Skip to content

Latest commit

 

History

History
167 lines (126 loc) · 4.94 KB

roles.md

File metadata and controls

167 lines (126 loc) · 4.94 KB

Prev: Methods
Next: Phasers


Section 8: Roles

This file is automatically generated. If you wish to submit a PR, do not edit this file directly. Please edit templates/rfc/roles.md instead. Use bin/generate_rfc.pl to regenerate the RFCs.


8.1 Background

Roles in Corinna are based on the Traits, composable units of behavior paper, we particularly find interest in Traits: the formal model.

In particular, according to the formal model, traits are designed to be both commutative (a+b=b+a) and associative ((a+b)+c=a+(b+c)). While we cannot perfectly guarantee this in the face of method modifiers, we will see to return to this model.

8.2 Overview

Roles are designed to share small sets of behaviors which address cross-cutting concerns. Roles may consume other roles, but they may not inherit from classes, nor may they be inherited from.

Roles may require one or more methods to be implemented. All abstract methods in roles are considered to be required. For Corinna, any forward declaration (a method declared without a body: method foo;) of a method is considered an abstract method.

role SomeRole {
    method foo;
    method bar :common;
    ...
}

Any non-private methods with method bodies are considered to be methods the role provides. These may be both class and instance methods.

Important required methods must not be listed with arguments. These are a syntax errors:

method foo ();
method bar ($baz);
role SomeRole {
    method foo ()          { ... } # instance method provided
    method bar :common ()  { ... } # class method provided
    method baz :private () { ... } # private methods are not provided
}

Any fields declared in the role are completely private unless standard field attributes are used to expose them

role SomeRole {
    field $name :reader; # ->name is provided
    field $age;          # private to this role
}

Roles may not access the fields or methods of the class the role is consumed into unless those have already been exposed in the public interface.

8.3 Example

It is entirely possible, for example, to want to have an identical mechanism to provide unique, repeatable UUIDs to different classes. It might look like this:

role Role::UUID {
    use Data::UUID;

    # these are private to this role
    my $uuid           = Data::UUID->new;
    my $namespace_uuid = $uuid->create_str;

    # abtract methods in roles are required
    method name;

    method uuid () {
        return $uuid->create_from_name_str( $namespace_uuid, $self->name );
    }
}

And to use that in your class:

class Person :does(Role::UUID) {
    field $name :param :reader;
}

And using that:

my $alice = Person->new(name => 'Alice');
say $alice->uuid;

The above will create a unique, repeatable UUID for a given Person.name (only repeats in a single process due to how UUIDs work).

Roles may consume other roles and classes may consume one or more roles. Any method name conflicts are fatal, if and only if the methods come from different namespaces.

role A :does(C) { method a () { ... } }
role B :does(C) { method b () { ... } }
role C          { method c () { ... } }

class SomeClass :does(A, B)   { ... }

Thus, in the above example, though A and B both pull in the c() method from role C, there is no conflict because it is the same method.

However, if SomeClass defined a c() method, there will be a conflict.

8.4 Aliasing and Excluding

For the MVP, we will not be providing aliasing and excluding of methods.

8.5 ADJUST and DESTRUCT

Both the ADJUST and DESTRUCT phasers will be allowed in roles. Class ADJUST phasers are called before its roles ADJUST phasers which are called before child ADJUST phasers and its roles phaswers.

DESTRUCT role phasers are called before class DESTRUCT phasers which are called before parent DESTRUCT phasers.

Important: for a given level of the inheritance hierarchy, if more than one role is consumed, the order in which its ADJUST and DESTRUCT phasers are called is not guaranteed.

8.6 Conflicts

Consuming multiple roles may result in method name conflicts. This is a fatal error. If a class consumes a role with a method name that conflicts with a role method, the class method wins, but a warning is issued.

8.7 Questions

8.7.1 Changing access level of role methods?

No role method marked as private should be composed into the consuming class or role. However, the consumer may need the behavior, but not want to expose it.

If a method exported by a role is public but the consumer does not wish to expose that part of its interface, should it have a way to adjust the access level to C?


Prev: Methods
Next: Phasers