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"; 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.
Re:And with Moose in Perl 5
masak on 2010-07-05T05:05:28
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 three quite different situations:
- Walking along the candidate list in of matching multi subs or methods.
- Walking up the inheritance tree in a class hierarchy.
- Unwrapping the candy wrapping of wrapped routines.
That's a way cool generalization, but the interactions between these three (or at least the first two) are still not very well-understood. It is thought that a class-hierarchy dispatcher will constitute the "outer loop", and a multi candidate dispatcher will form the "inner loop". We're still trying that model out for size.
Re:And with Moose in Perl 5
masak on 2010-07-07T22:37:05
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:And with Moose in Perl 5
perigrin on 2010-07-08T02:29:24
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:And with Moose in Perl 5
perigrin on 2010-07-08T03:03:36
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);
return $ret;
}
}I'm however unsure if callsame is implicitly a method dispatch or not. masak's example makes it seem like it is but the documentation is spares and semantically it seems like it should at *least* be
.callsame
if it's got an invocant.Re:And with Moose in Perl 5
perigrin on 2010-07-08T04:26:05
I mean
$.callsame
rather than.callsame
. Thanks to TimToady and TiMBuS for clarifying things at least enough that I understand that much.Re:And with Moose in Perl 5
masak on 2010-07-08T10:11:39
Yes, it's
$.callsame
. See this post for a slightly deeper "why" answer.