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.
Duck Typing
A common object design strategy in dynamic languages is Duck Typing. The cliché is
that if an object walks()
like a Duck
and
quacks()
like a Duck
, it must be a Duck
enough that the rest of the program can treat it like a Duck
. In
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.
False Cognates
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 can()
or 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
correct 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 Dog
,
check that the provided object performs the Dog
role. Similarly
for 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.
Not so sure about Perl but in Ruby you can solve a lot of this by simply designing your class hierarchy appropriately.
For example:
I think this is, in a lot of ways, practically the same as the idea of roles. You'd write code like:
We can essentially say that the object must at least behave like a Whatever. Is roles the Perl equivalent to this? I suppose if you wanted to say that a Dog is a Mammal and a Quadruped (or something) then just using a class hierarchy like this could get hairy. Still, I can't really imagine code that would do something like that... at least I wouldn't want to maintain it :)
It is not a part of Duck Typing to stick your head in the sand and use any old class because it has a method with the right name or even the right name and argument signature. There is no substitute for knowing your code!
You start with a false premise.
Writing static type checks into your dynamic programming language code to tie down the correct type for DT is not the way to go either.
If you know your code then you will know when you can safely Duck Type. To stretch your analogy, you are expected to investigate the code enough to find out if bark for a dog can be used in place of bark for a tree. If you don't, then don't blame Duck Typing the coder is just being work-shy.
Similarly with roles, it may help, but just because two types implement the same role what is to stop the role being an insufficient test of interoperability? Or of two implementations of a role being incompatible in a needed context?
Nothing beats reading the code behind the interface/role/method signature to see if things can fit. We all have finite resources so we can try and enforce 'contracts' of some sort, but please don't falsely denigrate DT because you want to plug roles. Roles should be able to stand on its own after all?
- Paddy.
I like the overview of roles, but your examples are a bit misleading imo.
I try to explain things in more detail here:
http://www.scwn.net/2009/05/duck-typing-and-roles-in-object-oriented-programming/
I'd like to hear your comments as well.
I'm inclined to agree with Paddy: "Similarly with roles, it may help, but just because two types implement the same role what is to stop the role being an insufficient test of interoperability? Or of two implementations of a role being incompatible in a needed context?" Just going from what you've been saying in your recent writings about roles, I keep getting the feeling that it's just a way of pushing the problem around. Is their anything that prevents two different programmers from implementing roles of the same name that perform different functions? What if you've got a Logging role that talks to STDERR and I've got one that talks to a database, or sends email (perhaps silently alerting Greenpeace). Unless you've got a centralized L'Académie Perlaise that dictates the usage of names aren't you always going to run into "false cognate" problems somewhere?