Augment and Inner: Haters Gotta Hate
The other day, on the Moose mailing list, I read this: “I’m not sure augment/inner is ever the right answer, ...”. It reminded me of this sentence from the POD for Moo:
... the author considers augment to be a bad idea ...
Here’s another:
Nobody understandsaugment
/inner
properly, and the whole idea is broken.
The authors of these statements are intelligent, experienced programmers. You can find similar statements all over the web, also made by intelligent, experienced programmers. It certainly sounds like this augment
thing is a pretty terrible idea. So why all the hate for augment
?
Perhaps it’s how the feature is described in the Moose documentation:
This modifier reverses the normal subclass to parent method resolution order. With anaugment
modifier the least specific method is called first. Each successive call toinner
descends the inheritance tree, ending at the most specific subclass.
Wow ... it reverses inheritance? Instead of going from subclass to base class (as a call to super
would do), it goes backwards?? Boy, that does sound like a bad idea.
When I first read about augment
, I wasn’t really sure how you could possibly use it. The canonical XML example seemed contrived—sort of like the canonical example for recursion being factorials, even though it’s obviously more sensible to calculate a factorial with a loop. The web page construction example in the main augment
documentation is only slightly better: it reminds me of the WRAPPER
/content
feature of Template Toolkit, which is certainly useful ... but, then, if I needed to do that, I’d just use TT, wouldn’t I? But I decided to keep an open mind. I filed it away in the back of my brain and vowed to try it out if I ever saw some problem that looked like a good fit.
And, eventually, I did.
Let me show you a pattern using augment
that I’ve used again and again, and you can judge for yourself whether it works well or not. This is a pattern involving command line utilities, in the svn
or git
style, where the utility accepts a number of “subcommands” which all do different (but related) things. It’s actually a pretty common thing to want to do, if you’re not the sort of person who has a tendency to just whip up a web page to solve trivial problems (hint: I’m not). And, happily (and as usual), we have a Perl module to solve the problem for us: the excellent App::Cmd by Ricardo Signes. It allows each subcommand to be a subclass of a common base class, opening up lots of possibilities for method sharing between subcommands: if you have common functionality that all subcommands need to be able to do, just stick it in the base class. Easy peasy. Of course, objects means Moose ... right? Is App::Cmd written in Moose? No ... but MooseX::App::Cmd (originally written by Yuval Kogman) is.
Excellent. So now we can have a pattern roughly like so:
class My::Command extends MooseX::App::Cmd::Command
{
# common stuff goes here
}
class My::Command::foo extends My::Command
{
method execute ($opt, $args)
{
# perform the duties of "mycommand foo"
}
}
class My::Command::bar extends My::Command
{
method execute ($opt, $args)
{
# perform the duties of "mycommand bar"
}
}
Cool. Now suppose I want to have all my commands print something common at the beginning and end of their individual functionality? Sort of like so:
class My::Command extends MooseX::App::Cmd::Command
{
# global options
has yes => (
traits => [qw< Getopt ENV >],
documentation => "Assume 'yes' for all confirmations and default values for all prompts.",
cmd_aliases => 'y',
env_prefix => 'MYCOMMAND',
is => 'ro', isa => 'Bool',
);
method print_cmdline_args ()
{
# print all command line switches,
# including defaults for those not specified
}
method verify_continue ()
{
print "Is this correct? (y/N) ";
exit unless <STDIN> =~ /^y/i;
}
}
class My::Command::foo extends My::Command
{
method execute ($opt, $args)
{
$self->print_cmdline_args;
$self->verify_continue unless $self->yes;
# perform the duties of "mycommand foo"
say "All done!";
}
}
class My::Command::bar extends My::Command
{
method execute ($opt, $args)
{
$self->print_cmdline_args;
$self->verify_continue unless $self->yes;
# perform the duties of "mycommand bar"
say "All done!";
}
}
Hmmmm ... not very DRY, eh? And it’ll get much worse if things need to be added to the common prefix and postfix. How can we get rid of all that repetition?
class My::Command extends MooseX::App::Cmd::Command
{
# global options
has yes => (
traits => [qw< Getopt ENV >],
documentation => "Assume 'yes' for all confirmations and default values for all prompts.",
cmd_aliases => 'y',
env_prefix => 'MYCOMMAND',
is => 'ro', isa => 'Bool',
);
method print_cmdline_args ()
{
# print all command line switches,
# including defaults for those not specified
}
method verify_continue ()
{
print "Is this correct? (y/N) ";
exit unless <STDIN> =~ /^y/i;
}
method cmd_prefix ()
{
$self->print_cmdline_args;
$self->verify_continue unless $self->yes;
}
method cmd_postfix ()
{
say "All done!";
}
}
class My::Command::foo extends My::Command
{
method execute ($opt, $args)
{
$self->cmd_prefix;
# perform the duties of "mycommand foo"
$self->cmd_postfix;
}
}
class My::Command::bar extends My::Command
{
method execute ($opt, $args)
{
$self->cmd_prefix;
# perform the duties of "mycommand bar"
$self->cmd_postfix;
}
}
Well, that’s better, certainly ... but still not awesome. Now, if I need to expand what the prefix and/or postfix do, all subcommands will automatically be updated, so that’s a win. But, still, every time I write a new subcommand, I (or whoever comes after me) has to remember to call
cmd_prefix
and cmd_postfix
. Annoying. If only there were a way to simplify it further ... make it even DRYer ...
class My::Command extends MooseX::App::Cmd::Command
{
# global options
has yes => (
traits => [qw< Getopt ENV >],
documentation => "Assume 'yes' for all confirmations and default values for all prompts.",
cmd_aliases => 'y',
env_prefix => 'MYCOMMAND',
is => 'ro', isa => 'Bool',
);
method print_cmdline_args ()
{
# print all command line switches,
# including defaults for those not specified
}
method verify_continue ()
{
print "Is this correct? (y/N) ";
exit unless <STDIN> =~ /^y/i;
}
method execute ($opt, $args)
{
$self->print_cmdline_args;
$self->verify_continue unless $self->yes;
inner();
say "All done!";
}
}
class My::Command::foo extends My::Command
{
augment execute
{
# perform the duties of "mycommand foo"
}
}
class My::Command::bar extends My::Command
{
augment execute
{
# perform the duties of "mycommand bar"
}
}
Ah, yes. Perfecto.
Notice the really sad thing about this: it’s a command-line script, where speed is at a premium; a place that might, in fact, be perfect for Moo ... except Moo can’t do that. Ah, well, there’s always Mouse ...
[Update: tobyink points out that you could always use MooX::Augment here.]
Are there other uses for augment
? Sure. How about a base class that needs to provide a consistent interface for returning the values of a hashref, but the values in the hashref must be supplied by the subclasses?
class baseclass
{
method actions ()
{
my $actions = inner();
return wantarray ? () : undef unless $actions;
return wantarray ? keys %$actions : $actions;
}
}
class subclass1
{
augment actions
{
return { foo => 'do some fooey things', bar => 'belly up to the bar' };
}
}
# etc
Now your subclasses can concentrate on the hash values (which is what they really care about anyway), and not have to worry about whether they’re fulfilling the contract of the return value’s interface, because the base class handles that. Nice.
These aren’t the only patterns for augment
/inner
that I’ve experimented with, but they’re the two that have ended up in production code that I’ve written. I’m sure there are others that I’ve yet to find. But, you know what? Even if there are only ever these two, that’s two areas of pain that I’ve been saved, merely by keeping an open mind about a feature that seemed at first to be useless. That’s not a bad return on investment, I’d say.
[Update: tobyink has pointed me to this node on PerlMonks, where Stevan Little (author of Moose) says about augment
:
Thankfully, only a small percentage of people actually grok this feature and of those people only a handful of them are crazy enough to try and use it.
I suppose I am forced to take this as a compliment. :-) ]
Side note: Assume that all code examples have the following at the top:
use 5.12.0;
use warnings;
use MooseX::Declare;
use Method::Signatures::Modifiers;
Although the code above looked fine when I first posted it, it has slowly devolved into near-illegibility, which seems to be a “feature” of blogs.perl.org. I’ve added a gist to make the code a bit easier to read.
In your second example, you could also be performing checks on the incoming arguments and the return value, making it into a Perl implementation of interfaces.
I've written a MooX::Augment module. It has issues, but then again so does the Moose implementation.
@preaction: Yes, excellent point.
@tobyink: Awesome pointers! I'll update the post to mention MooX::Augment, and thanx for all the additional quotes to use abusing augment. :-D
I like the first example: in a hierarchy that already exists, that's a great way to avoid duplication.
The second example could be implemented with a Role: you would require the actions() method, and build a common behavior "around()" it. Does augment/inner has advantages over the Role approach?
@mascip: Yes, you're right that in the second example a role would work as well. I believe the only advantage of augment in that case would be to save you an extra entity. If you needed to do that sort of thing more than once, the role would clearly be superior. If you only needed to do it the one time, one could make the argument that just keeping it in the existing class is simpler than spreading out the code across multiple entites, and possibly multiple files, causing readers to have jump back and forth between things to get the whole picture.
But the role is certainly a great alternative.