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
The I would get;extends 'Diamond_C', 'Diamond_B';
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 bitspackage Diamond_D; use Moose; use mro 'c3';
or I could just follow the advice from Modern Perl
But that reminds me of the 'advice' a Swiss mountain guide once passed on to me.
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 fromuse base
in thatuse base
will push onto@ISA
, and Moose just replaces the contents of@ISA
. Meaning that multiple calls touse base
will add to@ISA
, while multiple calls toextends
will simply re-write@ISA
.Other than that, I am enjoying these blog posts, keep up the good work!
Steve, Perhaps I should of put "Read in Gabor's" voice before
"And this is a problem?"
It was actually refreshing to see that Moose does not do any "magic" and keeps perl's DFS MRO rather than something different.
Nice to know about that subtle difference between 'base' and moose 'extends' as well.
and you gave me an idea for a future post.
Cheers
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.