Stories
Slash Boxes
Comments
NOTE: use Perl; is on undef hiatus. You can read content, but you can't post it. More info will be forthcoming forthcomingly.

All the Perl that's Practical to Extract and Report

use Perl Log In

Log In

[ Create a new account ]

Ovid (2709)

Ovid
  (email not shown publicly)
http://publius-ovidius.livejournal.com/
AOL IM: ovidperl (Add Buddy, Send Message)

Stuff with the Perl Foundation. A couple of patches in the Perl core. A few CPAN modules. That about sums it up.

Journal of Ovid (2709)

Saturday August 21, 2004
12:16 AM

Solving compositional problems with Perl 6 mixins

[ #20496 ]

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 I couldn't quite discard the idea. 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 (should not be instantiated) base class called Character that is subclassed by race (or species, if you prefer). Using a Perl6ish pseudo-code, we can imagine this:

class Elf is Character {
  ...
}

class Drow is Elf {
  ...
}

class Human is Character {
  ...
}

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 race gets a subclass 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:

class Thief is Profession {
  ...
}

my Elf $elf .= new(Thief.new);

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:

# guessing on the syntax here ...
class Elf is Character {
  has $:profession;
  method new(Class $class: Profession $profession) { #does this work
    $:profession = $profession;
    $profession.race(profession $this); # or this?
  }
  ...
}

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). Apparently roles that are assigned at runtime instead of compile-time are called "mixins" but they don't behave quite like Ruby mixins. Still, whatever they are called, they seem to simplify the problem. With mixins I can just do this:

$elf does Thief;

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:

class Elf is Character does Infravision {
  ...
}

class Gnome is Character does Infravision {
  ...
}

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.)

Bringing this back to reality, there'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. That really sucks when your manager doubles as a programmer (as our's sometimes does.) Instead, you can use mixins:

$employee does Programmer does Manager;

OK. That's enough for one night. Time to head to the club (and hope my pager doesn't go off.)

The Fine Print: The following comments are owned by whoever posted them. We are not responsible for them in any way.
 Full
 Abbreviated
 Hidden
More | Login | Reply
Loading... please wait.
  • 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.)

    Also, unlike Java's interfaces, role names and type names share a namespace. That's all of the goodness of allomorphic type checking in signatures.

    • I thought I had posted a response, but apparently I had not :(

      First, can you give a clear definition of "allomorphism"? Try as I might, I can't quite understand it. It seems like allomorphism [c2.com] is similar to polymorphism, except you can override behavior on an instance level instead of a class level and all that's relevant is that the interface is consistent. Is that correct? I think not because it's too simple.

      Also, I'm not sure what you mean by "role names and type names share a namespace." What be

  • At a glance, if I was going to design the class structure you were looking for, I'd start with something like this:

    - Character
      - Race
        - Elf (@ISA InfraVision)
          - Drow
        - Human
        - Dwarf
      - Class
        - Thief (sub pick_pocket)
        - Magician

      - ElvenThief (@ISA Elf, Thief)
      - DwarfThief (@ISA Drawf, Thief)

    - Skills (contains defaults skill level settings)
      - Vision
        - InfraVision
       - Thef

    • With all due respect, I think you've inadvertently illustrated my point. Let's stick with Perl 5 and assume that delegation is the key to performing profession-specific behavior, but we won't use delegation yet. We'll assume four races, human, elf, dwarf and halfling. We'll also assume four professions, fighter, thief, magician and cleric. With the Character and Profession abstract base classes, we have a grand total of ten classes, each which encapsulates the basic attributes of what they're trying to m

      • 1) I agree that a class structure for each class / race would get way out of hand. I fail to understand why you need a class structure to handle the problem in the first place. It seems to me that every character could attempt any behaviour, so you could just have a default set of skill levels, and perform procedural adjustment to those stats based on classes/races. I have no idea what methods / attributes would useful in individual class / race classes.

        2) If the class structure *is* needed for some reaso

        • Those are valid questions. The truth is, you never need objects to accomplish any programming problem. I chose objects for this illustration primarily because characters are really bundles of data with behaviors attached to them. This, of course, is one way objects are defined.

          As for how mixins solve the problem more elegantly, you might want to read a virtually identical node on Perlmonks [perlmonks.org]. In light of the responses, I back off a bit from asserting the utility of mixins once it became clear that they'

  • Also (now) wondering if the Decorator design pattern would do the trick. One of O'Reilly's sample chapters reminded me of this post...

    http://www.oreilly.com/catalog/hfdesignpat/chapter/index.html [oreilly.com]

    j