Solving compositional problems with Perl 6 rolesby Ovid (Cardinal)
|on Aug 22, 2004 at 01:50 UTC||Need Help??|
Originally posted on use.perl, then I tried to figure out why I hadn't posted it here.
I was thinking about my live testing demo and what sort of module to create when I discarded the idea of creating a D&D style Character class. Just showing the basics seemed too simple, but my brain just wouldn't let Character.pm die. When the idea of a profession (sometimes known as a character class, but the term is too confusing in this context) started causing problems, I realized that I was looking at a classic failing of traditional OO type systems.
Imagine an abstract base class called Character that is subclassed by race (or species, if you prefer). Using a Perl6, we can imagine this:
So far, all is well and good. But how do we create an Elven Thief? You don't want Thief to subclass from Elf because then each race gets a subclass for each profession and the number of classes quickly becomes ridiculous and you duplicate a lot of code. However, you can't have Elf subclass from Thief because not all elves are thieves unless you want to start toying with the idea of manipulating inheritance trees based upon an instance instead of a class (and all of the ridiculous problems that would bring.) Java interfaces are of no use because those are assigned at compile time and, in any event, don't provide the implementation.
So you have two unpleasant alternatives. A traditional one is to use delegation. The simplest method is to provide a "profession" slot that stores a Profession object:
But this is yet another problem. Now the elf has a reference to a Thief object but the abilities of the thief are tied to the abilities of the character instance, so the thief now has a dependency on an instance of a Character, so it probably stores the elf in a slot:
This, of course, means a circular reference which might have to be broken explicitly and, in any event, guarantees that classes are coupled more tightly than I like and I have to violate the Law of Demeter if I want to do $elf.profession.pick_pocket($mark);. Naturally this breaks down pretty quickly when the elf is a magician and gets his fingers broken (or worse.)
Ruby style mixins can help, but their ordering problems are well known and sometimes can cause difficulties that still force delegation.
Perl 6 roles seem to solve the problem (see Apocalypse 12). Roles that are assigned at runtime instead of compile-time are called "mixins" and they can apply to an instace of a class! Still, whatever they are called, they seem to simplify the problem. With mixins I can just do this:$elf does Thief; # hmm ... that reads funny
And the Thief methods are automatically available to the $elf. Because inheritance is not involved, you don't have the ordering problems of multiple inheritance, nor do you have to worry about other classes picking up the undesirable trait (of being a thief.) Because interfaces are not used, you don't have the problem with duplicate code, but you do get the benefit of knowing the methods are implemented (and that required methods are available.) Because delegation is not involved, there are no "Law of Demeter" concerns, nor are the maintenance or performance penalties paid. All things considered, this seems to be a huge win. (mixins are apparently implemented in Perl 6 as anonymous classes attached to an instance but I don't know the implications of that.)
As it turns out, roles are still quite useful. Remember how some races, such as gnomes and elves, had infravision? Rather than reimplement:
This is all so spectacularly useful that I'm surprised more people haven't wanted it, though I admit it's a new idea (and I've described it rather incompletely.)
And to wrap this up by bringing this back to reality, here's a classic OO example of bad inheritance. If you have an Employee class, how do you represent programmers and managers? One idea is to make two subclasses. This fails when your manager doubles as a programmer (as our's sometimes does.) Instead, you can use mixins:$employee does Programmer does Manager;
OO can be very difficult to get right and that's part of the reason we've had so many variations on OO over the decades. Roles, mixins, and other goodies are a fascinating experiment in giving programmers the tools to do things right.