Skip to content

Latest commit

 

History

History
371 lines (275 loc) · 12.8 KB

roles.pod

File metadata and controls

371 lines (275 loc) · 12.8 KB

A role is a standalone, named, reusable unit of behavior. You can compose a role into a class at compile time or add it to an individual object at runtime.

That's an abstract definition best explained by an example. This program demonstrates a simple and pluggable IRC bot framework which understands a few simple commands.

What is a role?

Previous chapters have explained classes and grammars. A role is another type of package. Like classes and grammars, a role can contain methods (including named regexes) and attributes. However, a role cannot stand on its own. To use a role, you must incorporate it into an object, a class, or a grammar.

In traditional object orientation, classes perform two tasks. They represent entities in the system by providing models from which to create instances. They also provide a mechanism for code re-use. These two tasks are somewhat in tension. For optimal re-use, classes should be small, but in order to represent a complex entity with many behaviors, classes tend to grow large.

Perl 6 classes retain the responsibility for instance management. Roles handle the task of code reuse. A role contains the methods and attributes required to provide a named, reusable unit of behavior. Building a class out of roles uses a safe mechanism called flattening composition. You may also apply a role to an individual object. Both of these design techniques appear in the example code.

Roles may also support parameters. This helps Perl 6 provide generic programming, along the lines of generics in C# and Java, or templates in C++.

Compile Time Composition

Take a look at the KarmaKeeper class definition. The body is empty; the class defines no attributes or methods of its own. The class inherits from IRCBot, using the is trait modifier -- something familiar from earlier chapters -- but it also uses the does trait modifier to compose two roles into the class.

The process of role composition is simple. Perl takes the attributes and methods defined in each role and copies them into the class. After composition, the class appears as if it had defined those behaviors itself. This is part of the flattening property: after composing a role into the class, the roles in and of themselves are only important when querying the class to determine if it performs the role. Querying the methods of the KarmaKeeper class through introspection will report that the class has both a process method and an on-message multi method.

If this were all that roles provided, they'd have few advantages over inheritance or mixins. Roles get much more interesting in the case of a conflict. Consider the class definition:

Both the AnswerToAll and AnswerIfTalkedTo roles provide a method named process. Even though they share a name, the methods perform semantically different behaviors--behaviors which conflict. The role composer will produce a compile-time error about this conflict, asking the programmer to provide a resolution.

Multiple inheritance and mixin mechanisms rarely provide this degree of conflict resolution. In those situations, the order of inheritance or mixin decides which method wins. All possible roles are equal in role composition.

What can you do if there is a conflict? In this case, it makes little sense to compose both of the roles into a class. The programmer here has made a mistake and should choose to compose only one role to provide the desired behavior. An alternative way to resolve a conflict is to write a method with the same name in the class body itself:

If the role composer detects a method with the same name in the class body, it will then disregard all of the (possibly conflicting) ones from the roles. Put simply, methods in the class always win.

Multi-methods and composition

Sometimes it's okay to have multiple methods of the same name, provided they have different signatures such that the multidispatch mechanism can distinguish between them. Multi methods with the same name from different roles will not be in conflict. Instead, the candidates from all of the roles will combine together during composition into the class.

If the class provides a method of the same name that is also multi, then the multi candidates from the class will be included. On the other hand, if the class has a method of the same name that is not declared as a multi, then the method in the class alone--as usual--will win.

This is the mechanism by which a class that composes both, for example, the KarmaTracking role and the Oping role would end up having the candidates that both roles provide for the on-message method. As a class ends up composing more than a couple of roles, it may be preferable to use an alternative syntax that allows you to list the roles in the class body:

Calling all candidates

This needs an index tag or two, but I'm not sure which.

The process methods of the roles AnswerToAll and AnswerIfTalkedTo use a modified syntax for calling methods:

The use of .* instead of . changes the semantics of the method dispatch. Just as the * quantifier in regexes means "zero or more", the .* dispatch operator will call zero or more matching methods. If no on-message multi candidates match, the call will not produce an error. If more than one on-message multi candidate matches, Perl will call all of them, whether found by multiple dispatch, searching the inheritance hierarchy, or both.

There are two other variants. .+ greedily calls all methods but dies if there is not at least one method that matches. .?, which tries to call one method, but returns a Failure rather then throwing an exception. The zero-or-more and one-or-more dispatch forms are somewhat rare, but very useful in event driven programming. One-or-failure is very useful when dealing with per-object role application.

Expressing requirements

The role AnswerIfTalkedTo declares a stub for the method bot-nick, but never gives an implementation.

In the context of a role, this techniques declares that any class which composes this role must somehow provide a method named bot-nick. The class itself may provide it, another role must provide it, or a parent class must provide it. IRCBot does the latter; it IRCBot defines an attribute $!bot-nick along with an accessor method.

What about AUTOMETH?

You are not required to make explicit the methods on which your role depends. If you do so, the role composer can detect any errors at compile time. If you do not, the problem will only appear at runtime, when and if something attempts to call the missing method.

Runtime Mixins

I don't like the term "mixin" for this.

Class-based OO usually works, where instances have the methods and attributes of the classes they instantiate. Sometimes, something more dynamic is useful. Perl 6 allows you to add extra behaviors to individual objects by applying roles to individual objects at runtime.

The example in this chapter uses this in order to give bots new abilities during its lifetime. The Plugins role is at the heart of this. Note the signature of the method on-message. It captures the invocant into a variable $self marked rw, which indicates that the invocant may be modified. Inside the method, that happens:

The "first-class roles" mention needs to move out of a parenthetical note.

Like classes, roles are first-class in Perl 6; you can pass them around just like any other object. The %pluggables hash maps names of plug-ins to Role objects. Thus this lookup stores a Role in $plug-in. The does operator adds this role to $self--not the class of $self, but the instance itself. From this point on, $self now has all of the methods from the role, in addition to all of the ones that it had before. Importantly, this does not have any influence on any other instances of the same class; only this one instance has changed.

Differences from compile time composition

Runtime application differs from compile time composition in that methods in the applied role in will automatically override any of the same name within the class of the object. It's as if you had written an anonymous subclass of the current class of the object that composed the role into it. This means that .* will find both those methods that mixed into the object from one or more roles along with any that already existed in the class.

If you wish to apply multiple roles at a time, list them all with does. This case behaves the same way as compile-time composition, in that the role composer will compose them all into the imaginary anonymous subclass. As you might expect, any conflicts will occur at this point.

This gives a degree of safety, but it happens at runtime and is thus not as safe as compile time composition. For safety, perform your compositions at compile time. For example, instead of applying multiple roles to an instance, compose them into a new role at compile time and apply that role to the instance.

The but operator

Parametric Roles

POD ERRORS

Hey! The above document had some coding errors, which are explained below:

Around line 1:

Unknown directive: =head0

Around line 245:

=end for without matching =begin. (Stack: [empty])

Around line 293:

=end for without matching =begin. (Stack: [empty])

Around line 306:

=end for without matching =begin. (Stack: [empty])

Around line 334:

=end for without matching =begin. (Stack: [empty])