The Why of Perl Roles explains some of the motivations behind the inclusion of roles in Perl 6 and their implementation in Perl 5 through Moose. Perl Roles Versus Inheritance compares the design and intent of using roles to "traditional" subclassing inheritance.
A common object design strategy in dynamic languages is Duck Typing. The cliché is
that if an object
walks() like a
quacks() like a
Duck, it must be a
enough that the rest of the program can treat it like a
other words, the presence of a method with a name that you recognize from
some context is proof enough that the object responds to that method
in a way that makes sense in the context you had in mind.
That often works.
Sometimes it doesn't work. Suppose (and yes, this is already a cliché in Perl circles) that your program models a park. You have a
Dog class. You have a
Tree class. Given an object for which you don't immediately know its type -- assume you're using a dynamic language or that your genericity system performs type erasure and you're effectively using a dynamic language -- do you know which
bark() method is appropriate in any given situation?
This is the false cognate problem; the name of a method is not always sufficient to determine its intended meaning.
Duck Typing and Type Checking
Defense-minded duck typers soon realize that blindly calling methods on objects of unknown type is a recipe for disaster. (To be fair, a well-organized program runs into this problem rarely. Even in the absence of strict typing -- or manifest types -- it's rare not to know the types of objects you expect within specific scopes in the program. Then again, I don't add error checking to my programs because I expect exceptional conditions to occur frequently.)
One approach is to use object introspection to see if the object in question really does support the required method. In Perl, this is:
croak 'Duck typing failure' unless $dog_or_tree->can( 'bark' );
Or in Ruby, something like:
raise 'Duck typing failure' unless dog_or_tree.respond_to?( 'bark' )
If you want to get stricter, and if your language supports this, you can even check the arity or types of the allowed signatures of the method -- but look at all of the boilerplate code you have to write to make this work. That's also code to check only a single method.
Suppose you want to call several methods on the object. You could check
respond_to? for each of them... but at this point, people often check the class inheritance of the object, in Perl with:
croak 'Duck typing failure' unless $dog_or_tree->isa( 'Dog' );
Or in Ruby, somethin like:
raise 'Duck typing failure' unless dog_or_tree.is_a?( 'Dog' );
Of course, this precludes other ways in which your object can perform the
bark() methods: reimplementing it, mixing it in from
elsewhere, delegating to it, or composing it -- unless you explicitly lie to
the rest of the system about the identity of your object by overriding
isa() in Perl or
is_a? in Ruby.
Liars tend to get caught, and the results can be messy.
Roles, False Cognates, and Identity
In the context of a
Dog, it's obvious what
bark() means. In the context of a
Tree, it's obvious what
bark() means. Without that context, you just don't know.
Roles add that context back. If you want to deal with a
check that the provided object performs the
Dog role. Similarly
Tree. Instead of asking "Do you provide a method with this
name?", ask "Do you perform this role?" The latter question avoids false
cognates and ensures that the class representing the provided object fulfills
the contract required by that role at compilation time.
As well, you don't have to check the inheritance structure of a given object. It doesn't matter. The most important lesson of duck typing is that any object which provides an interface you both understand appropriately should be substitutable for any other object which provides that well-understood interface. How that object fulfills that interface is its own business.
Roles provide a way for developers to name a collection of behavior and then refer to objects -- generically -- in terms of whether they provide that collection of behavior. The ad hoc, free-form nature of duck typing is great for providing future extensibility; it doesn't lock your code into a rigid hierarchy that can prove brittle during future maintenance.
However, duck typing sometimes fails to provide enough information about necessary meaning and context, and the workarounds to make a duck typed program more robust can subvert the goals of duck typing.
Next time, I'll compare roles to (Java) interfaces.