Role::Basic - what does DOES do?

Over the years it has become abundantly clear to me that people who object to OO fall into two categories:

  1. A handful of people who really understand object-oriented programming.
  2. Wankers with blogs.

Though I don't object to OO, it's still unclear which of the above categories I fall into. I suspect it's not the first.

This brings me to a lovely quote from H. L Mencken, "for every complex problem, there is a solution that is simple, neat, and wrong" (annoyingly, there are tons of subtle variations on that quote. I need to find the original). A "solution" I had in Role::Basic was was simple, neat, and wrong. I'm kicking myself, but my problem was my failure to apply what I thought I had "learned" from the original traits papers.

While porting Moose role tests to Role::Basic, I encountered the following tests. I was pretty happy because these were easy to convert over and I knew I was going to pass them.

package My::Role;
use Moose::Role;

sub foo { "FOO" }
sub bar { "BAR" }

package My::Role::Again;
use Moose::Role;

with 'My::Role' => {
    -alias    => { foo => 'baz', bar => 'gorch' },
    -excludes => ['foo', 'bar'],
};

package My::Class::Again;
use Moose;

with 'My::Role::Again';

my $x = My::Class::Again->new;
isa_ok($x, 'My::Class::Again');
does_ok($x, 'My::Role::Again');
does_ok($x, 'My::Role');

can_ok($x, $_) for qw[baz gorch];

ok(!$x->can($_), '... cant call method ' . $_) for qw[foo bar];

is($x->baz, 'FOO', '... got the right value');
is($x->gorch, 'BAR', '... got the right value');

Nope. Turns out I had the same bug which Moose used to have back in 2008:

ok(!$x->can($_), '... cant call method ' . $_) for qw[foo bar];

I don't know why Moose had that but, I know why Role::Basic has this bug and I aim to fix it over the weekend. Specifically, it's because I wasn't paying attention to the underlying research and in thinking about it, I suspect that the DOES may not be implemented correctly.

To apply roles, I used this Menckenish code (actually, I didn't, but it's conceptually the same):

my @role_names = $class->fetch_all_roles($target);
foreach my $role_name (@role_names) {
    $class->apply_role( $target, $role_name );
}

(Again, highly simplified example of the concept)

As I applied each role, I would take a look at the -excludes and -alias parameters and ensure that I respected them properly. However, I found that as roles composed other roles, I wasn't properly propagating the exclusions and aliases. That's obviously a problem, but how does one fix it?

By remembering the original traits research. As I noted a couple of days ago in What is a conflict?, a trait's identity is bound to the services it provides, not the trait's name. "Bob" and "Alice" might be the same person with a cross-dressing fetish. More importantly for us, two guys named "John" aren't necessarily the same guy.

In my code above, while I tried to respect aliasing and exclusion, I was mostly relying on trait names, not the traits themselves (Moose solves this by properly creating composite traits). I now have a partial local fix of this problem by respecting the fact that a trait is the services it provides, not its name.

That brings me to the problem of "DOES" (pseudo-code):

ClassA does RoleA -foo { // RoleA but excluding foo()
    method foo() {...}
}

ClassB does RoleA {
}

Both ClassA and ClassB should return "true" for $class.DOES('RoleA'), but now we're relying on the name, not the actual trait. By definition, the above classes compose different traits. It turned out to be a mistake for me in Role::Basic internals to rely on the name; is it also a mistake in DOES?

I've been bitten many times by blithely ignoring issues and assuming they won't matter in the "real world". It reminds me of a time, many years ago, when I complained about MI being used to implement plugins in Catalyst and a dev told me something like "we're re-imagining OO". Well, it turns out that this bad idea was a bad idea and now they're re-imagining Catalyst:

Many reusable extensions which would previously have been plugins or base classes are better implemented as Moose roles..

Yet another time I was working on a project when a developer thought to solve a problem via something he called "chained inheritance". This was effectively diddling the inheritance tree at runtime. There is some dispute as to whether inheritance should be a core concept in OO, but there is little dispute that encapsulation (isolation!) should be a core concept and inheritance is a whopping violator of encapsulation. His simple and neat solution offered plenty of bugs due to the encapsulation violation and I vetoed it.

I'm not immune to this. I knew that AUTOLOAD was bad, but the first (non-public) version of my HTML::TokeParser::Simple module used it and I thought I was being clever by ignoring something I was told was bad. You will no longer find AUTOLOAD in that module.

This all boils down to me channeling my inner Dominus: "DON'T IGNORE STUFF YOU DON'T UNDERSTAND, RETARDO!".

So I thought I was being smart with my role application, but I was ignoring the foundation of roles in doing so. As a result, a bug appeared. I'm working hard to ensure that I better understand the original theory behind roles and I'm going to swallow my pride and not just "assume" I know what I'm doing.

But I still don't quite know what DOES should do.

1 Comment

Actually, multiple inheritance as a plugin system isn't necessarily a bad idea - in many cases, it works extremely well.

Both Catalyst and DBIx::Class did fine that way for some years - in the case of Catalyst, having already moved to be Moose based, there's simply no reason not to use roles rather than multiple inheritance - and a role based system seems to be easier for developers to reason about.

Of course, Role::Basic's refusal to permit method modifiers renders it entirely useless for such a plugin system since the entire point of them is to wrap application class methods to modify behaviour.

Therefore, if considering multiple inheritance versus roles for a plugin system, I would have to strongly recommend that you only consider a fully featured role system, such as those contained in Moose, Mouse or the Role::Tiny class in my Moo distribution.

-- mst, out.

Leave a comment

About Ovid

user-pic Have Perl; Will Travel. Freelance Perl/Testing/Agile consultant. Photo by http://www.circle23.com/. Warning: that site is not safe for work. The photographer is a good friend of mine, though, and it's appropriate to credit his work.