Beefy Boxes and Bandwidth Generously Provided by pair Networks
good chemistry is complicated,
and a little bit messy -LW
 
PerlMonks  

Solving compositional problems with Perl 6 roles

by Ovid (Cardinal)
on Aug 22, 2004 at 01:50 UTC ( #384858=perlmeditation: print w/ replies, xml ) 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:

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 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:

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:

class Elf is Character { has $:profession; method new(Class $class: Profession $profession) { $:profession = $profession; # how the heck do I access the instance from within new()? $profession.race($_); # certainly incorrect syntax } ... }

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:

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

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.

Cheers,
Ovid

New address of my CGI Course.

Comment on Solving compositional problems with Perl 6 roles
Select or Download Code
Re: Solving compositional problems with Perl 6 roles
by BrowserUk (Pope) on Aug 22, 2004 at 02:39 UTC
    $elf does Thief; # hmm ... that reads funny

    Maybe that's because when we coudn't divorce the person from their actions, naming a (sub)class Thief made sense, but now we can have groups of actions that can be enacted by a range of People, maybe we should name such groups by the collective term for those actions:

    my Elf $elf does Thieving;

    And

    my $indivual is Employee has HomeAddress, TelephoneNo, ParkingSpace does Program, Manage, FirstAid;

    I wonder if P6 will allow lists to does, is, has and the like? It would certainly make the syntax more friendly.

    Reads quite nicely, and seems quite intuative that if the employee gives up first aiding, removing that Role has little impact on the rest of his persona. Maybe:

    $individual stops FirstAiding;

    Then that dear old Elf from earlier sees the light and repents:

    $elf stops Thieving;

    Of course, it doesn't flow completely right. There would be no point in:

    $indivual does Thieving;

    Unless the compiler has the smarts to turn that into:

    $individual isn't Employee;

    :)


    Examine what is said, not who speaks.
    "Efficiency is intelligent laziness." -David Dunham
    "Think for yourself!" - Abigail
    "Memory, processor, disk in that order on the hardware side. Algorithm, algorithm, algorithm on the code side." - tachyon
      That would imply that the possession of one trait implies the removal of another. Now, you're starting to talk Prolog-style levels of compiler knowledge. While this wouldn't necessarily be a bad thing, and would be implemented in the definition of the trait, I can see how this would be quite reasonably construed as some serious action-at-a-distance ...

      ------
      We are the carpenters and bricklayers of the Information Age.

      Then there are Damian modules.... *sigh* ... that's not about being less-lazy -- that's about being on some really good drugs -- you know, there is no spoon. - flyingmoose

      I shouldn't have to say this, but any code, unless otherwise stated, is untested

        I'm not sure what you mean by "That..."?

        ... does Program, Manage, FirstAid;

        was meant to imply the equivalent of

        ... does Program does Manage does FirstAid;

        with $individual stops FirstAid; removing the latter Role when appropriate.

        Unless your referring to the last bit about the compiler mapping $individual does Thieving; to $individual isn't Employee;, in which case, my attempt at humour failed completely.


        Examine what is said, not who speaks.
        "Efficiency is intelligent laziness." -David Dunham
        "Think for yourself!" - Abigail
        "Memory, processor, disk in that order on the hardware side. Algorithm, algorithm, algorithm on the code side." - tachyon

      I wonder if P6 will allow lists to does, is, has and the like? It would certainly make the syntax more friendly.

      Yes, but how would it be implemented? When you have a variadic operator, how do you avoid slurping up the rest of the line? If only one argument is allowed and the operator always return the object it's acting on, the current chaining idiom works.

      Cheers,
      Ovid

      New address of my CGI Course.

        I'll admit to having very little idea of what problems that would cause for parsing. I guess the list could be bracketed, but that wouldn't look so nice.

        Another alternative is to have the list terminated by the presence of the next keyword which may be possible, but could also be a problem depending on how the parser is implemented.

        Another is that the absence of a comma after the final argument is indicative of the end of the parameters for that operator, so the next token must be a new operator (or the end of statement.

        SQL parsers seem happy to cope with comma seperated lists of args between keywords. How much difference there is between that and parsing Perl I'm not sure.


        Examine what is said, not who speaks.
        "Efficiency is intelligent laziness." -David Dunham
        "Think for yourself!" - Abigail
        "Memory, processor, disk in that order on the hardware side. Algorithm, algorithm, algorithm on the code side." - tachyon
Re: Solving compositional problems with Perl 6 roles
by kappa (Chaplain) on Aug 22, 2004 at 09:37 UTC
    Is there a method to resolve ambiguities when $elf does Thief does Scout and both Thief and Scout can hide, only differently?

      Well, it depends. If you are using roles, there are a couple of ways of resolving conflicts, but basically it works out as:

      class Elf is Character does Thief does Scout { method hide { .Scout::hide(@_) } ... }

      However, if you prefer, you can have your cake and eat it too.

      method hide ($self: $action) { # $self is required here because the topicalizer assigns # $action to $_ given $action { when Skulking { $self.Thief::hide($action) } when Stalking { $self.Scount::hide($action) } } }

      And that's nice because theoretically, if your elf knows various professions, she should know how the activities with those professions vary, just as one person might know how to drive a race car but wouldn't think of doing that with a dump truck, even though both activies are driving.

      Unfortunately, when it comes to your specific example with mixins, I'm a little less clear about disambiguation. When you use a role at runtime on an instance, you get new anonymous classes that are related to the instance via inheritance. Thus, the following has the inheritance ordering problem again:

      $elf does Thief does Sentry;

      In that example, $elf.hide calls Sentry.hide as Thief.hide is further up the inheritance tree. This seems to limit the utility of mixins, but I can't be sure. Further clarification would be nice.

      Cheers,
      Ovid

      New address of my CGI Course.

        Ultimately, it appears that the crux of the matter is defining what is a behavior vs. what is a trait.

        A thief IS a Character who "implements/HAS" the behavior/interface of methods associated with "thieving". An Elf is a Character who IS a type of Character, who HAS certain additional traits.

        It's all going to come down to whether you think of the character as an "elf type-of thief," or a "thief type-of elf." This will determine which is the child class by inheritance, and which is the child by "mix-in".

        Just some thoughts,
        -v
        "Perl. There is no substitute."
        Your interesting node made me reread the Apocalypse. I stand admired (again) and await being able to call ALL the matching methods from all the roles of my little elf (via $elf.*hide) or even choose which ones I like to skip.
Re: Solving compositional problems with Perl 6 roles
by fergal (Chaplain) on Aug 23, 2004 at 08:53 UTC
    with the idea of manipulating inheritance trees based upon an instance instead of a class (and all of the ridiculous problems that would bring.)
    Cecil does this and seems to do it very nicely, although I've never used it, I just read their language specification.

    I'm curious about this bit of code

    $elf does Thief; # hmm ... that reads funny
    is this attaching the methods at run time or at compile time? That is, do the methods stay attached to the object inside $elf or is it only treated as doing Thief in scope of this declaration? The second case would not be very useful and the first case basically amounts to "manipulating inheritance trees based upon an instance".
      There're two syntaxes proposed in the Apocalypse 12. $elf does Thief make that very $elf a Thief. And $elf but Theif creates a cloned elf which becomes a Thief. I eagerly await writing something like:
      my $parser = new XML::Parser but Simple; # or my $mail = new MIME but Lite;
      :)
      The first case doesn't manipulate the inheritance trees based upon an instance. For one thing, the Perl6 model won't affect instance2 just because instance1 does Foo. That is probably the biggest difference.

      The second thing is that the trait Thief will not override methods that already exist in the inheritance tree of $elf. It will add the Thief methods as a fallback, should they be needed. And, it does it until you remove the Thief trait. A12 has a lot to say on the topic, which is good.

      ------
      We are the carpenters and bricklayers of the Information Age.

      Then there are Damian modules.... *sigh* ... that's not about being less-lazy -- that's about being on some really good drugs -- you know, there is no spoon. - flyingmoose

      I shouldn't have to say this, but any code, unless otherwise stated, is untested

        The first case doesn't manipulate the inheritance trees based upon an instance. For one thing, the Perl6 model won't affect instance2 just because instance1 does Foo. That is probably the biggest difference.

        That's exactly what I would call manipulating the inheritance tree based upon an instance. The only other meaning I can think of (and I think it's the meaning you thought I meant) is that twiddling instance1 has an effect on all other instances. This would be an extremely pointless feature and isn't what I was talking about.

        So to quote Ovid again (the whole sentence this time)

        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.)
        that reads to me like individual objects would have their own inheritance trees so some Elfs would subclass from Thief and some wouldn't. He calls this ridiculous but isn't that exactly what Perl 6 does?
Re: Solving compositional problems with Perl 6 roles
by Anonymous Monk on Aug 24, 2004 at 18:15 UTC
    This is all rather good, but I want a level 9 wizard myself. Thieves aren't THAT fun and they die pretty quick.

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlmeditation [id://384858]
Approved by elusion
Front-paged by Enlil
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others perusing the Monastery: (5)
As of 2014-10-22 00:00 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    For retirement, I am banking on:










    Results (112 votes), past polls