A class hierarchy of expression nodes: it's so much the prototypical use case for run-time method polymorphism that it's almost a cliché. One can close one's eyes and picture the way parts of the expression tree interact in rich, complex ways, shaped by the very types of the nodes themselves, in a dynamic dance of late bindings and virtual methods. Switch statmement, get thee behind me. Et cetera.
I'm building one. And I'm having almost too much fun doing it. In between trying to use the strengths of Perl 6 and keeping true to the original program I'm porting, I've discovered an important thing: Ovid is right about roles.
Specifically, I'm having trouble picturing how I would cram all the type information into my expression node class hierarchy, were I not using roles. The roles definitely help manage complexity in my case.
Here's a pretty diagram of my class hierarchy.
It's a flat beast. Apart from everything deriving from Exp
, I have only one case of old-skool inheritance in the diagram. And even that one is more making a point than actually shortening the code.
Then there's all the colorful dots, representing the roles I'm mixing into my types. Some are for convenience (like the blue ones), others are vital for my program (like the green ones), and the rest are somewhere in between on the convenient/vital scale.
I even have a case of inheritance between two of the roles! Which means, in practice, that those classes with an orange dot also act as if they had a red dot. Very handy.
During the infancy of Rakudo, I've gotten used to learning to live without various features. Were I to do what I'm doing here without using roles, I could use two other mechanisms. The first is regular inheritance. The very thought gives me a bit of vertigo; I don't think I'd be able to turn the colored dots into base classes. Definitely not all of them at once; I'd have to choose. And that choice would affect the entire design of the program, probably resulting in loss of clarity.
The second way I could compensate for not having roles would be by using .can
a lot. The presence of a given role in a class is isomorphic to the presence of a given method in a class. So that would definitely work, but I don't think I would like it as much. There's something to be said for declaring is
and does
relationships at the very top of the class declaration.
All in all, I'm very happy about the way things work. I'm wondering whether, had I not read all of Ovid's posts on managing the complexity of class hierarchies with roles, I would have come up with this design myself. Maybe, maybe not. But anyway: thanks, Ovid! This rocks!
A still-open question for me is whether the topmost type, Exp
, should be a class or a role. Synopsis 12 has this to say about when to use roles:
Classes are primarily for instance management, not code reuse.
Consider using
roles
when you simply want to factor out
common code.
I am using Exp
for code reuse, and for giving all of the other classes in the hierarchy a common base type. So I guess I could indeed turn it into a role. But it's just that... I don't see a reason to do so, and I still feel instinctively reluctant about it. Maybe I'm a bit hung up about it being a class hierarchy.
This point has come up before on IRC, and I've yet to hear a satisfactory way to resolve it: when faced with making a base type a class or a role, which way should one go?
Briefly, the difference most people make between roles and inheritance is that inheritance should be used for class responsibility and roles should be used for cross-cutting concerns. For example, if you have a Dog class, it should inherit from Mammal rather that have Mammal being a role, but since many unrelated classes might need serialization, a role makes more sense.
At work, I still use inheritance with roles because I tend to be a bit more cautious at work than I do in my posts, but I do have one critical (and fairly large) subsection I wrote where everything was composed with roles and no inheritance was provided (even in one case where inheritance seemed the right way to go). As a result, I had some lovely composition-time safety that I could not have gotten via inheritance.
While I have read papers explaining when to use each, I've yet to see the downside of a "role only" OO implementation (if Eiffel was single-inheritance, that would be a better argument for mixing classes and roles because its inheritance is much safer than Perl's).
Re:Roles and Inheritance
masak on 2010-01-17T11:49:20
Ok, what you're saying fits my experience as well. It seems like "role only" OO should be able to work, but there also seems to be no compelling reason to make everything into roles.