porton porton - 5 months ago 31
Perl Question

Moose::Role weirdness with overridden methods

Base.pm
:

package Base;
use Moose::Role;

sub f {
my ($self) = @_;
print "In role.\n";
}

1;


X.pm
:

package X;
use Moose;

with 'Base';

around 'f' => sub {
my ($next, $self) = @_;
print "Nevermind, we are in class X.\n";
};

__PACKAGE__->meta->make_immutable;
1;


Y.pm
:

package Y;
use Moose;

with 'Base';

override 'f' => sub {
my ($self) = @_;
print "Nevermind, we are in class Y.\n";
};

__PACKAGE__->meta->make_immutable;
1;


Then X does work and Y does not. It is a weird design, as
override
is just a special case of
around
and as a special case should work also.

Can anyone explain why this design decision and why it is so weird?

$ perl X.pm
$ perl Y.pm
Cannot add an override method if a local method is already present at /usr/lib/i386-linux-gnu/perl5/5.22/Moose/Exporter.pm line 419
Moose::override('f', 'CODE(0x9c002f8)') called at Y.pm line 9

Answer

The documentation describes override as:

An override method is a way of explicitly saying "I am overriding this method from my superclass". You can call super within this method, and it will work as expected. The same thing can be accomplished with a normal method call and the SUPER:: pseudo-package; it is really your choice.

What you are doing contradicts this definition. With a role, f is installed in your package. You are attempting to define another f in the same package.

The fact that your role is called Base indicates to me that you have some confusion when it comes to the difference between inheritance versus composition.

The purpose of around is to wrap a method in the current class regardless of whether it was implemented in the same package or inherited or composed:

Method modifiers can be used to add behavior to methods without modifying the definition of those methods.

Just a straightforward reading of these two snippets makes the distinction clear to me.

When you apply the role that defines f, that itself overrides any inherited method f. If you then say override 'f', you are declaring your intention to override f again. Well, there can only be one method f in one class. Which one should count? The one that you get by applying the role, or the one you just defined? For all intents and purposes, the methods you get from composing a role are just like methods you defined manually in the class. There is no reason a priori one should be more important than the other.

Think of this as airline travel. Think of the method modifiers as classes: First, business, economy etc. So, in first class, get_dinner_menu maybe wrapped with appetizers, sweets, desserts etc.

On the other hand, override is like changing the flight. It's as if you are saying "I want to fly on both TK 1 and UIA 213". Makes no sense.

Maybe the following script will make things a bit clearer by showing a naive implementation of around and overriding a method without using override.

#!/usr/bin/env perl

use strict;
use warnings;

{
    package MyRole;

    use Moose::Role;

    sub f {
        print "in role\n";
    }
}

{
    package X;
    use Moose;

    with 'MyRole';

    around 'f' => sub {
        my ($orig, $self) = @_;
        print "In wrapper\n";
        return $self->$orig( @_ );
    };

    {
        my $f = \&f;
        {
            no warnings 'redefine';
            *f = sub {
                my ($self) = @_;
                print "In wrapper wrapper\n";
                return $self->$f( @_ );
            }
        }
    }
}

{
    package Y;

    use Moose;
    with 'MyRole';

    sub f {
        print "In overridden method\n";
    }
}

print '=-=' x 22, "\n";

my $x = X->new;
$x->f;

print '=-=' x 22, "\n";

my $y = Y->new;
$y->f;

Output:

=-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-= 
In wrapper wrapper                                                 
In wrapper                                                         
in role                                                            
=-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-==-= 
In overridden method