Search code examples
perlmoose

Perl Moose augment vs around


Trying to understand Moose:

use Modern::Perl;

package FOO {
    use Moose;
    sub rep { say "  <report></report>"; }
    sub doc {
        say "<document>";
        inner();
        say "</document>";
    }
}

package BAR {
    use Moose;
    extends 'FOO';

    around 'rep' => sub {
        my $orig = shift;
        my $self = shift;
        say "<document>";
        $self->$orig(@_);
        say "</document>";
    };

    augment 'doc' => sub {
        say "  <report></report>";
    };
}

package main {
    BAR->new->rep;
    say "===";
    BAR->new->doc;
}

Produces...

<document>
  <report></report>
</document>
===
<document>
  <report></report>
</document>

... the same result. When desinging the "model (object hierarchy)" - based on what I should decide when to use around and when augment?

Here are probably other (deeper) things what i currently didn't understand yet.

Can please someone provide an "more deep" explanation, because reading tru the Moose/Manual/MethodModifiers obviously not helped enough...


Solution

  • augment and around do rather different things. augment is designed to make this sort of pattern easier:

    package Document {
      use Moose;
      sub make_document {
        my $self = shift;
        return "<doc>" . $self->_document_innards . "</doc>"
      }
      # stub; override in child class
      sub _document_innards {
        my $self = shift;
        return "";
      }
    }
    
    package Invoice {
      use Moose;
      extends 'Document';
      sub _document_innards {
        my $self = shift;
        return "Give me money!";
      }
    }
    

    With augment it becomes:

    package Document {
      use Moose;
      sub make_document {
        my $self = shift;
        return "<doc>" . inner() . "</doc>"
      }
    }
    
    package Invoice {
      use Moose;
      extends 'Document';
      augment make_document => sub {
        my $self = shift;
        return "Give me money!";
      };
    }
    

    On the other hand, around is used as a replacement for doing $self->SUPER::method(@args) because SUPER can't work in roles (the notion of which package to check superclasses for is bound at compile-time, so $self->SUPER::method(@args) would check superclasses of the role (i.e. none) instead of superclasses of the class that consumed the role. If you're not using roles, then SUPER can still be used in Moose classes just fine. TLDR: SUPER is broken by roles, so Moose gives you around as an alternative.

    Another thing to compare is override which is a bit like around, but gives you this super() function which is perhaps slightly cleaner than $self->$orig(@_). It also has an "there can be only one" feature. If two roles try to provide an around modifier for the same method, that's fine: they both get to wrap the method (though the order in which they are applied is undefined). If two roles try to provide an override modifier, that's an error.

    The implementation of augment is somewhat fragile in my experience, so that in my book is a reason to avoid it. Don't try to replace it with around, because they do rather different things. Instead, replace it with the pattern used in my first example above.