Diamonds are a Moose's Best Friend

I was happily enjoying my usual playing about when I made a slight change to my nice new Moose class and suddenly my thingy no longer worked as it could no longer couple with my gonkulator to produce a splork.

Fortunately I am an old school perl programmer and spotted the problem right away. It seems Moose's 'extends' works the same way 'use base', or if you want to go real old school push(@Some::Class,"Some::Other::Class") , does. In other words, the order in which you get something out, in this case an inherited sub, depends on the order it goes in.

So I was wondering how moose handles The Diamond Problem. In other words given a class diagram like this
     {A}
     /   \
    {B}   {C}
     \   /
     {D}

We have D inherits from B,C which both Inherit from A. How does Moose handle this. Is it as I expect the same as trad perl or is it different.

So here is our little diamond in Moose
{
    package Diamond_A;
    use Moose;
    has 'hello' => (
        is  => 'rw',
        isa => 'Str',
        default=>'Diamond_A::hello');
    no Moose;
    __PACKAGE__->meta->make_immutable;
}
{
    package Diamond_B;
    use Moose;
 
    extends 'Diamond_A';
    no Moose;
    __PACKAGE__->meta->make_immutable;
}
{
    package Diamond_C;
    use Moose;
 
    extends 'Diamond_A';   
    has 'hello' => (
        is  => 'rw',
        isa => 'Str',
    default=>'Diamond_c::hello');
    no Moose;
    __PACKAGE__->meta->make_immutable;
   
}
{
    package Diamond_D;
    use Moose;
    extends 'Diamond_B', 'Diamond_C';
    #use mro 'c3';
}

If we did this;

my $d = Diamond_D->new();
print "hi=".$d->hello."\n";

our result would be

hi=Diamond_A::hello

Which is what one would expect as that is the default for perl, (D, B, A, C). The problem being that A appears before C, even though C is the subclass of A. If I changed my D extends to
extends 'Diamond_C', 'Diamond_B';
The I would get;
hi=Diamond_c::hello

As I now have (D,C,B,A). I said I spotted this right away as it is a rather old school idiom.

And this is a problem?

In the end I was sort of expecting Moose to add a little more autobymajikally on the MRO side not just the default, depth first search of trad perl, perhaps default use of MRO::c3, or at least a way to use it by default.

I can of course just add this to the offending bits
package Diamond_D;
    use Moose;
    use mro 'c3';

or I could just follow the advice from Modern Perl

Avoid multiple inheritance when possible.


But that reminds me of the 'advice' a Swiss mountain guide once passed on to me.


Never use anchors! They may come out!

3 Comments

So couple things I want to point out.

First, Moose has always avoided being "magical", meaning it is built on a foundation of well researched science instead of dark incantations and goat entrails. While it might seem magical in places, when broken down it is all fairly straightforward and conceptually solid (heck, I stole most of it from LISP and those folks know their stuff).

Second, when you have roles, you shouldn't need multiple inheritance (except for very, very rare cases, … in which you might want to actually rethink your design instead of reaching for MI). This is why I didn't bother making 'c3' MRO by default (also note that I wrote the original Class::C3 in 2005 as an experiment (or "joke", which ever you prefer), and Moose was not written until 2006).

Lastly, Moose's extends actually differs from use base in that use base will push onto @ISA, and Moose just replaces the contents of @ISA. Meaning that multiple calls to use base will add to @ISA, while multiple calls to extends will simply re-write @ISA.

Other than that, I am enjoying these blog posts, keep up the good work!

One way to think about this is to realize that a subclass should be viewed as a more specific instance of its parent class. That's why cat would inherit from mammal instead of the other way around.

When you have multiple inheritance, that "more specific instance" paradigm often becomes questionable. When I see MI used, it's usually not because the subclass is a more specific instance of multiple parents, but more like a genetic child-parent relationship where you're hoping to pick up behavior from each parent. But unlike real-life genetics, you often get *all* behavior from both parents, even if you didn't want it.

So what does it mean when you're using inheritance to get at some other behavior? You're looking at a potentially cross-cutting concern that's better suited for a role than a parent.

Leave a comment

About byterock

user-pic Long time Perl guy, a few CPAN mods allot of work on DBD::Oracle and a few YAPC presentations