Are roles only semantically different from inheritance?

I've been using roles lately and was going to write about them on perltricks.com. However I've come into difficulty in explaining how roles are functionally different from inheritance.

I've found that the typical features described about roles in contrast to inheritance are:


  • Avoid complex inheritance hierarchies

  • Better encapsulation and code re-use by focusing on "does" over "is"

  • Enforce polymorphism through "requires"

The difficulty I'm finding is that all of these features can be provided though inheritance already.

Avoid complex inheritance hierarchies. This is equivalent to only allowing one level of inheritance. Besides, roles can use other roles (at least they can in Roles::Tiny), so this problem is not avoided by using roles. This concept can also be confused with package hierarchies (e.g. File::Text) versus inheritance hierarchies -> clearly subclasses can inherit from modules outside of their package hierarchy, hence the risks of multiple inheritance.

Better encapsulation and code re-use. To find if a class does something, you just use the UNIVERSAL->can method. To find if a class has inherited a particular class, use the isa method. This doesn't seem different to roles.

Requires - this is useful, but why couldn't this be implemented as a module on it's own - in fact Roles::Tiny uses the UNIVERSAL->can method to check the symbol table for any required method / sub.

Given the above, are roles just a different way of describing inheritance, rather than a functionally different approach to OO polymorphism and encapsulation?


7 Comments

When we on go then we go to understanding
that role decrease complexity, if I found good module that realized role - I will use it

Though you can achieve similar things using inheritance, specially the messy multiple one, roles are a very different beast.
I often compare them to Java interfaces with code, or more accurately to monkey patching done right. They get in lined in to your consuming classes, leaving them flat and free of any inheritance tree. One of the coolest thing about them is that you can apply them to instances, not just classes.

They're really not the same, though they are both ways of implementing polymorphism.

First of all, roles are composed into classes. This means that all of a role's methods end up in the class as if they'd been defined there. There's no need to ever call "SUPER::foo". When a class consumes multiple roles, method conflicts can be detected at compile time and the role mechanism can throw an error. This forces developers to manually resolve the conflict, unlike subclassing. I suppose in theory you could write a subclassing mechinism like use parent that also did this, but why bother?

Second, roles don't have a constructor. This elements the bizarre evil of abstract base classes which can't be inherited, but look semantically like classes which can be constructed.

Of course, this being Perl, there's nothing you can't do with roles that you can't do with multiple inheritance. It's just that roles provide better, safer semantics for sharing code across multiple classes.

My take is that roles/traits provide "horizontal" composition of state and behaviors across (possibly unrelated) classes, while inheritance is a "vertical" composition between parent and child.

You're missing out on the allomorphic behavior of roles.

Consider the non-inheritance relationship, where one object delegates to another. You can't get the right answer about a type question by asking "Does the delegator inherit from the delegatee?", and you know that if you limit all of your questions about object behavior to "Does this object have a method of this name?" then you run into the false cognate problem (a tree has bark while a dog can bark; a student can bomb a test while a fighter jet can bomb a test target).

What you need is a way of asking "What named collection of behaviors does this entity provide?" without running into the false cognate problem and without dictating the nature of relationship between the entity in question and whatever you think provides that named collection of behaviors.

By consuming a role, you promise that the consumer provides the named behavior defined by that role. Consuming a role often flattens one or more methods into the consumer, but if the consumer already provides those methods, the flattening doesn't have to happen.

Roles give you a way of grouping collections of attributes and behavior under names as well as a default mechanism to provide that collection of behavior in whole or in part to a consumer. That's it.

Avoid complex inheritance hierarchies. This is equivalent to only allowing one level of inheritance.

Yes, but then in order to do non-trivial specialisation you have to start inheriting from multiple things, like you would compose multiple roles. And then you run into problems because multiple superclasses can provide the same method – which one do you call? Do you call both? Do the two methods on those two superclasses mean the same thing?

But with roles you do not have this problem. Why? Because roles become part of the class that consumes them, and you cannot compose multiple roles into a class if they provide methods of the same name (you will get an error at compile time) – not without explicitly disambiguating them.

Besides, roles can use other roles (at least they can in Roles::Tiny), so this problem is not avoided by using roles.

Yes, but it is not a problem to have that kind of hierarchy. Why? Same reason: because roles are not inherited by other roles, they are composed into them – again detecting errors at compile time and demanding explicit disambiguation.

Better encapsulation and code re-use. To find if a class does something, you just use the UNIVERSAL->can method. To find if a class has inherited a particular class, use the isa method. This doesn’t seem different to roles.

What this bit mostly means is that because you do not inherit from roles, they can be used like interfaces in Java: you declare a bunch of requirements the consuming class must fulfil, and then someone else can specify the interface instead of a class where they say what type they expect, which works because any class implementing the interface, well, implements the interface. This was Java’s attempt to provide something like multiple inheritance, without providing actual (dangerous) multiple inheritance.

Roles are better because they can include default implementations – with Java-style interfaces, every class conforming to an interface is forced to contain a separate implementation of it, even if it’s boilerplate.

Obviously multiple inheritance doesn’t have that problem. Instead, it has other problems – much much worse problems – problems that roles also don’t have.

So basically:

Think of roles as multiple inheritance without the inheritance – something that gives you the benefits that multiple inheritance promises, except through a different mechanism that works without the indirection of inheritance, and therefore inherently lacks the method dispatch clusterfuck potential of complex MI hierarchies: MI without the diamond problem.

(As a side benefit, because you have two different kinds of entities (classes and roles), you get a clear distinction between concrete entities that can be instantiated (classes) vs conceptual ones that can’t (roles). If you use MI in the same style, everything is just classes, just some of them happen to be abstract, and there’s nothing inherently keeping you from trying to instantiate those, even though it’s useless (and if it works (which it may), a bug).)

Inheritance is (or should be) about is-a relationships fulfilling Liskov's substitution principle.

One example that I have seen about roles is GuardDog class example: in Dog class hierarchy, and consuming Guard role (which can be consumed by many different unrelated classes).

Leave a comment

About sillymoose

user-pic