Working with pls (a next-gen project installer for the Perl 6 ecosystem), I had a few classes with code like this:
class POC::Tester does App::Pls::Tester {
method test($project --> Result) {
my $target-dir = "cache/$project<name>";
if "$target-dir/Makefile" !~~:e {
return failure;
}
unless run-logged( relative-to($target-dir, "make test"),
:step('test'),:$project ) {
return failure;
}
return success;
}
}
(success and failure are Result enum values defined elsewhere. They felt like pleasant documentation, and when return type checking works, they'll even help catch errors!)
Now, I wanted to add super-simple progress diagnostics to this method. I wanted an announce-start-of('test', $project); at the start of the module, and either an announce-end-of('test', success); or an announce-end-of('test', failure);, depending on the success or failure of the method.
I have a low threshold for boilerplate. After realizing that I'd have to manually add those calls in the beginning of the method, and before each return — and not only in this method, but in several others — I thought "man, I shouldn't have to tolerate this. This is Perl 6, it should be able to do better!"
So I thought about what I really wanted to do. I wanted some sort of... method wrapper. Didn't really want a subclass, and a regular role wouldn't cut it (because class methods override same-named role methods).
Then it struck me: mixins. Did those already work in Rakudo? Oh well, try it and see. So I created this role:
role POC::TestAnnouncer {
method test($project --> Result) {
announce-start-of('test', $project<name>);
my $result = callsame;
announce-end-of('test', $result);
return $result;
}
}
And then, later:
POC::Tester.new() does POC::TestAnnouncer
And it worked! On the first attempt! jnthn++!
(If you're wondering what in the above method that does the wrapping — it's the callsame call in the middle. It delegates back to the overridden method. Note that with this tactic, I get to write my announce-start-of and announce-end-of calls exactly once. I don't have to go hunting for all the various places in the original code where a return is made.)
I guess this counts as using mixins to do Aspect-Oriented Programming. This way of working certainly makes the code less scattered and tangled.
So, in this file, I currently have a veritable curry of dependency injection, behavior-adding roles, lexical subs inside methods, AOP-esque mixins, and a MAIN sub. They mix together to create something really tasty. And it all runs, today, under Rakudo HEAD.
As jnthn said earlier today, it's pretty cool that a script of 400 LoC, together with a 230-LoC module, make up a whole working installer. With so little code, it almost doesn't feel like coding.
And with Moose in Perl 5 (Score:1)
package POC::TestAnnouncer;
use Moose::Role;
before test => sub {
my $project = $_[1];
announce-start-of('test', $project{name});
};
after test => sub {
my $project = $_[1];
announce-end-of('test', $project{name});
};
And then there's the more powerful around:
package POC::TestAnnouncer;
use Moose::Role;
around test => sub {
my $fn = shift;
my $self = shift;
my $project = $_[0];
announce-start-of('test', $project{name});
--fREW
http://blog.afoolishmanifesto.com
Re: (Score:1)
Thanks. I was only aware of how Moose did it to the extent that people come into the #perl6 channel sometimes and ask "what's the equivalent to Moose's before/after/around in Perl 6?" and we answer "[call|next][same|with]".
(The "call-" methods are returning calls, and the "next-" methods are tailcalls. "-same" sends along the original arguments, and "-with" allows you to send new ones.)
What's really neat, and what I still haven't quite gotten my head wrapped around, is that these four routines are used in t
Re: (Score:1)
Oh, huh, and I should probably add that mixins may look like they do a variant of wrapping in Perl 6, but they really work by creating an anonymous subclass to the class of the object, and re-blessing the object to that subclass. So, they work by inheritance, not wrapping.
The fact that both of these use "callsame;" to delegate to the original routine means that I can use mixins and think they are wrappers, and they'll still behave as I expect. That's why I like the above unification.
Re: (Score:1)
The Moose version Frew posted uses Roles. So applying this "mixin" at Runtime to an instance will also derive an anonymous subclass with the Role applied and re-bless an instance into that subclass.
Re: (Score:1)
Also playing about some I came up with something nearly identical using MooseX::Declare's syntax.
role POC::TestAnnouncer {
use mro;
method test ($project) {
announce-start-of('test', $project{name});
my $result = $self->maybe::next::method($project); # callsame
announce-end-of('test', $result);
Re: (Score:1)
I mean
$.callsamerather than.callsame. Thanks to TimToady and TiMBuS for clarifying things at least enough that I understand that much.Re: (Score:1)
Yes, it's
$.callsame. See this post [perl.org] for a slightly deeper "why" answer.