A significant new part of Perl 6’s object model and type system is the addition of roles. Part of their origin is an implementation in Smalltalk (there called traits). They also solve some systemic problems of other OO systems.

Why are they useful and how do they work?

What is a Role?

A role is a named collection of behavior — a set of methods identified by a unique name. This resembles a class or a type, in that referring to the role refers to the combined set of behaviors, but it is more general than a class and more concrete than a type.

Put another way, a role is an assertion about a set of capabilities. For example, a Doglike role identifies the important behavior that any doglike entity will possess: perhaps a tail attribute and the methods wag() and bark().

Even only this much is a great advantage to using roles. Presuming you have a method that needs something Doglike, you can ask “Does the class or object I receive behave in a Doglike fashion?” and take the appropriate action.

Bad Approaches to The Problem

You might already have encountered this problem. A common pattern is to create an abstract Dog object and requiring that any doglike object inherit from that Dog object. This allows you to check that the object or class you receive inherits from Dog.

This “solves” the problem in a bad way in all but very simple systems.

Problems with Inheritance

In a single-inheritance system, where a class can inherit from one and only one ancestor, the abstract base class strategy takes away your option of inheriting from any other ancestor, even if another would make more sense.

In a multiple-inheritance system, if you need to mark a particular class as Dog-like you now have the potential for weird method dispatch resolution errors. Can you guarantee that you’ll only ever get the right methods at the right times, or that no one will ever add another method to an ancestor class and override the right method? Suppose Dog itself extends Mammal and someone adds a method to that with the same name as a method in a class appearing after Dog in your class’s list of ancestors. Changes are fragile and the effects can appear far, far away.

Even further, there’s a design smell with this approach: it forces a particular class design strategy upon other classes. You know that you have the behavior of a Dog because of inheritance, but there are many other ways to get Dog-like behavior. You can reimplement the methods yourself. You can use a delegation or proxying relationship to contain one or many Dog objects and wrap all accesses to their methods. You can use a mock-Dog pattern for testing.

Requiring that all doglike entities actually specifically inherit from Dog–when all your code really cares about is that the objects or classes behave in a Doglike way–worries too much about how the classes and objects behave, not what they do. Inheritance is one way to ensure that a class or object implements a known interface appropriately, but the important part of polymorphism is that you don’t know how an implementation works, only that it does the right thing.

Forcing the use of inheritance outside of your classes is like expecting every iterator to maintain an internal stack of items to return or every webserver to be Apache httpd, rather than being able to call next_element() or speak HTTP.

(Perl 5 allows you to overload isa() to get past poorly-designed systems that violate the encapsulation of classes and objects they merely use, but there are plenty of error patterns around that method too.)

Problems with Duck Typing

Other systems wisely eschew checking the structure and implementation of objects and classes–specifically, how they provide an interface–but substitute checks for the presence of specifically-named methods. This is duck typing (or very loose ).

Duck typing argues that anything that can bark() is obviously a Dog. That works sometimes, but imagine that your system includes, besides the Dog class, a Tree class. Tree objects have the bark attribute available through the bark() accessor.

You can call the method bark() on a Tree, just as you can on a Dog, but the two methods, however similar in name, have completely different semantics. They’re false cognates. Objects of either class are not substitutable. Duck typing ignores this.

In practice, this may not often be a problem. Duck typing advocates argue, correctly, that effective testing strategies catch such errors in the rare cases when they occur. However, giving up the association between class name and method name is a loss of expressivity. There are already perfectly-serviceable namespaces for both the Dog’s bark() and the Tree’s bark(). Why should a language lose that information if it’s already in the system? Why should an implementation ignore that information, if it’s easy to use?

Problems with Java Interfaces

The designers of Java apparently identified this problem, but invented new problems when trying to solve it.

Java interfaces are abstract, named collections of method signatures (and little else). They allow you to identify a collection of behavior with a name and request that a class or object implement that behavior.

The problems come mostly from the poor design and implementation. Classes and interfaces occupy separate namespaces in Java, to some degree. The syntax for querying that an object or class extends another class is different from the syntax for querying that an object or class implements an interface.

This approach also requires that you know too much about how an object or class performs specific behaviors.

This is most painful when dealing with code you cannot change. If the code explicitly requires that a passed-in object or class inherit from another class–that is, the function signature specifically names a class or error-checking code at the start of the function explicitly checks that the argument is or inherits from a specific class, you cannot pass in a class or object that uses another object strategy. You’re stuck. Likewise, if the immutable code requires that your class or object implement an interface, that’s your only option. You’re slightly better off that way, but it’s not perfect either — and it’s rarely the default case, as it takes much more work to define an interface and use it than to define a class.

If the library requires a specific class, not an interface, and someone has declared that class final, you’re in real trouble. This is perhaps the worst possible case with all of Java’s language features. This anti-social behavior is evident even in parts of the Java standard library.

Another sin of Java’s interfaces is that they disallow code reuse. When the option is between allowing object implementation strategies other than inheritance but duplicating code and forcing the use of inheritance in related code but not duplicating code, people tend to take the somewhat-obvious latter choice.

Then again, Java has always been verbose, and the current state of the art for Java programming appears to be to use ever-more-powerful IDEs to generate boilerplate code, so one argument is to use better tools to work around a language’s design deficiencies.

What Roles Actually Do

What actually is a role?

It’s a named set of capabilities, with optional default behavior.

What is a class? It’s an instantiable set of one or more roles.

What is a type? It’s a role. It’s a named collection of behavior.

What’s a program? It’s a set of instructions for manipulating data in terms of roles — collections of supported behavior.

A role is an interface, in the behavior sense. It’s a guarantee that anything for which the question “Are you Doglike?” is true will behave in a Dog-like way.

More concretely, it’s the difference between saying “This method only ever takes String objects and prints them to a log file” and saying “This method only ever takes Stringifiable things, stringifies them, and prints them to a log file.” The former cares how it works. The latter cares that it works.

Perl makes this easier with automatic coercion when you use an object in a string context, but you have to remember to overload the stringification operator. Languages without automatic coercion make this much more difficult.

Roles can also provide behavior. This is an option, not a requirement. A Serializable role can provide default implementations that know how to inspect hash-based objects in Perl 5, while allowing for other objects or roles to perform the Serializable role for array-based objects, for example. To the outside world, it only matters that objects of both roles are Serializable, not that one uses a hash and one uses an array.

This is the second great philosophical realization about roles: not all useful behavior fits into inheritance hierarchies. Code reuse is more important than inheritance, so there should be ways to reuse code in situations where inheritance is not useful or helpful or necessary. As well, decomposing collections of behavior into roles can help to improve design in such a way that a class can compose only the roles it needs directly, rather than accidentally inheriting several unrelated methods.

Role Implementation Notes

There are two important parts of working with roles. The first is marking a class or object as performing a role. The second is querying that a class or object performs a role.

One possibility for marking a class or object as performing a role is allowing the compiler to detect this automatically, based on duck typing likelihoods. This suffers from the same false cognate problem as normal duck typing; it’s unlikely that static analysis could identify all of the potential roles in a system as well as identify good names for them. (Names are for humans, not computers.)

A likelier solution is to require specific declaration of roles and annotation of classes and objects that perform those roles. This is an implementation detail of the class, so it’s okay to use different syntax for “this class extends this ancestor” and “this class implements a role”.

Maintaining the distinction for the querying side may help people design better code by being more thoughtful about roles. In addition, there may be cases where explicit queries about inheritance relationships may be useful (perhaps in reflection or editor-support analyses). Using a separate meta-method, such as does(), seems effective.

As for the actual implementation, marking a class as performing a role adds the behavior of that role to the class immediately, at compile-time. For each method the role provides, that method becomes an available method on the class with the role’s implementation, unless the class already provides that method.

Obviously a class can provide its own implementations for every method of a role and still implement that role — the same goes for delegation or aggregation relationships. The role resolver merely has to know, when it runs, that the class unambiguously performs the behavior of the role.

Roles may have dependencies as well. Resolving them happens at compile time. A role may require that the class implementing that role also implement other roles. A Sortable role may require the implementation of a Comparable role, for example, to be sure that any existing compare() method does the right thing. Checking only for the existence of a compare() method may suffer from the false cognate problem.

Of course, the ambiguous bark() problem needs a solution as well. The role resolver must detect conflicts in role method names and require disambiguation. When you try to define a DogwoodTree that does both Doglike and Treelike, you must disambiguate explicitly, whether composing in one method or the other or providing your own bark() method that redispatches to the appropriate parent depending on context.

Advanced Roles

For optimal benefit in the system, any type checking (such as in method signatures) should query for roles first, then inheritance. A type system that cares more about capabilities than structural identity removes an entire world of pain–specifically the case when you need to work with a library you cannot modify that did not explicitly disallow the use of roles.

Now you have to use more syntax and do more work to prevent people from using roles.

Classes Imply Roles

If the does() question falls back to the isa() question (that is, if a type check first queries that the entity performs a role and then that it or its class inherits from an ancestor), there’s an implicit relationship implied between roles and classes.

In other words, every class explicitly defined in the system should implicitly declare a role of the same name. More likely, classes and roles occupy the same namespace. A class is a specialization of a role that is instantiable. Both classes and roles provide names for and define collections of behavior.

This also implies that it should be possible to declare that one class performs the role of another class. A CustomerProxy class may explicitly say that it does Customer. Regardless of the object strategy the code uses, the type system should consider that it behaves as a Customer and is usable any place that requests a Customer without modifying that code.

In a language with class-implies-role and a role-based type system, this requires almost no syntax:

    class SomeClass
    {
        method some_method { ... }
    }

        class AnotherClass does SomeClass
        {
             method some_method {  ... }
        }

The unmodifiable library problem moves further away.

Runtime Application

In a robust object system with a capable meta-model (basically a formalized system for customizing the behavior of classes and objects), it’s even possible to apply a role to an object at runtime.

Why would you do this? The obvious answers are decorating an object with logging or debugging code, but consider all of the uses of the Decorator pattern. I recently built a small game using this pattern. Each enemy element had two parts, a shape and color and movement behavior. The visual portion used a specific role and the movement portion used another role. The game used the Factory pattern to create enemies and applied a random visual and movement role to each. Thus I only had to create an abstract role for each role type and several concrete roles for each type of movement or visuals. Adding the roles to the objects allowed for several combinations of behavior — n times m combinations, where I only had to write n plus m roles.

In systems that do not allow runtime generation of new classes, it’s still possible to get some of the benefit of runtime role application with code generation and more use of the Factory pattern.

Parameterization

As long as a role provides the appropriate methods at role composition time, does it really matter how the role provides those methods? In a sufficiently dynamic language, it could generate them based on the phase of the moon (probably not useful apart from a Lycanthropic role), the program’s configuration file (much more useful), or even arguments passed to the role.

If you’re going to factor out common code into a role, why not go one step further and allow the parameterization of that code? When you apply a role to a class or an object, you can add arguments to the role to gain even more specific behavior.

For example, if you have a Loggable role, why not pass a filehandle or the name of a log file to the role at composition time? If you have a role that performs internationalization and localization, why not pass the name of the string library or the language to use?

There are plenty of possibilities and dozens of potential patterns awaiting discovery and categorization.

Conclusion

Despite the heavy insistence of too many OO tutorials, inheritance isn’t the fundamental principle of object orientation. Polymorphism is. Supporting roles at the language level itself provides yet another way of promoting polymorphism and providing design tools to build better systems. (Yet polymorphism based strongly on class identity loses most of its power; it’s allomorphism that matters — nominal typing over structural typing.)

Thinking in terms of capabilities and named collections of behavior can help you divide your systems into smaller, self-contained entities. These don’t have to be classes themselves, nor do they necessarily have to have ancestor or descendant relationships with other entities in your system.

Decreased coupling, increased cohesion, improved code reuse, and more ubiquitous and automatic polymorphism are fine design goals. Roles encourage them.