If you've followed Ovid's use Perl journal recently (and you should), you've seen a lot of practical discussion over the use of Perl roles in his work at the Beeb. There's also plenty of theoretical discussion.

I joined the Perl 6 design team in 2003. I spent a lot of time thinking about problems in every object system I'd ever seen. When we released Apocalypse 12 in 2004, we included a new feature called roles. The design borrowed from a Smalltalk idea called Traits, but (as usual) we Perl people have our own take on things.

Rakudo Perl 6 provides a very usable implementation of roles, and the Moose object system for Perl 5 allows you to use them as well.

Roles are different from so-called traditional class-based or prototype-based or even multidispatch object systems in several important ways.

Role Goals

In my mind, object orientation provides two essential features: encapsulation and polymorphism. A well-designed object oriented system models domain concepts along well-defined boundaries, hiding internal details from the outside world and treating like concepts as like concepts because they share the same interfaces.

Careful readers will note that the word "inheritance" does not appear in that paragraph. That is no accident.

Polymorphism is an interesting concept. I see it as providing genericity and extensibility. I may not know all of the potential uses for an object when I create it, but I need to have an accurate idea of how the object behaves. I need to be able to name that collection of behaviors. If I've created the object well, the collection of behaviors the object provides has a meaningful and understandable name.

A role is a name for a discrete collection of behaviors.

When you want to perform operations on an object, you need to know what kinds of behaviors that object supports. Is it a GUI widget? Does it represent a customer? Can you introspect it? Can you serialize it? Does it know how to log debugging information?

In effect, you're asking the object "What do you do? Do you perform this role?"

Once you start asking that question, you can (and should) stop caring about how the object performs its role. I've said nothing about inheritance, or delegation, or composition, or allomorphism. Those are mechanisms. Those mechanisms should be well-encapsulated inside the object where you can't poke or prod at them because they're none of your business.

The important question is not "Do you have a method called log_debug_output ?" or "Do you inherit from DebugLogger ?" but "Do you perform the DebugLogging role?"

That's subtle, so let me repeat it a different way. If you write code mindful of roles and you don't know the specific class of an object you receive but you want to call a method called log_debug_output on that object and have it behave as you expect, you want to check that that object performs the DebugLogging role. It doesn't matter how the object has that method. It could inherit it from a superclass. It could mix it in from a collection of unbound accessory methods. It could delegate it to a contained object or proxy it to a remote object. It could reimplement the method directly. It could compose it in from a role. It doesn't matter how the object has that method -- that's none of your business outside of the object -- only that it does have the method, and that it understands that method in terms of the DebugLogging role.

That last part is also subtle. The duck typing hypothesis suggests that method names alone are suitable to determine appropriate behavior. Roles avoids problems there by requiring disambiguation through naming. A TreeLike role's bark method has an obviously different context from a DogLike role's bark method.

Roles allow you to express (or require) context-specific semantics, especially when combined with your type system. A role-aware type system allows you to express yourself with better genericity: as long as you hew to a well-defined interface and do not violate the encapsulation of objects, you can enforce well-typed programs based on the specific behavior of objects regardless of their implementation.

This is very theoretical. Don't worry; I'll show specific examples in future entries.

Role Features

Roles are more than just tagged collections of behavior. You can think of them as partial, uninstantiable classes.

Roles can provide default implementations of behavior they require. By composing a role into a class, you can import its methods (and other attributes) into the class directly at compile time. There are rules for disambiguation and conflict resolution and requirements and provisions, but you get the rough idea. A role also provides a mechanism for code reuse.

Parametric roles take the concept even further by customizing their composable behavior based on arguments provided at composition time. They're intensely powerful.

The final -- and perhaps most subtle -- feature of roles comes from building them into your type system. Every class implies the existence of a role. If you declare a DebugLogging class, other code can access the DebugLogging role. They may not be able to compose in behavior from that class -- unless you write the class to make that possible -- but they can say that they perform the DebugLogging role, with all of the concomitant role composition checks to enforce this, to produce an object which may successfully substitute for a DebugLogging object anywhere that expects a DebugLogging object -- even though there's no formal relationship between the original class and the class which performs its role.

As I said, this is powerful but theoretical. Tomorrow I'll discuss Perl roles versus inheritance.