Perl Advent Calendar 2009-12-01

Not a creature was stirring…

by Jerrad Pierce

Minor corrections prompted by the Mo[ou]se team, who really want you to use the larger beast. — Jerrad 2009-12-02

Not even a large and gangly Moose. As you are probably aware, Moose is a relatively nifty OO framework. However, it is somewhat hefty: 6,361 lines of code plus 14 dependencies (5,199 LOC).1

Most of its fans will tell you not to worry about that, and of course premature optimization is the root of all coal-stuffed stockings, but what if you want some shiny new toys that are a bit more svelte? Enter Mouse. Mouse is "Moose without the antlers" i.e; lacking the thorny dependencies and added heft giving you a pain in the neck.

Mouse clocks in at 5.7 kLOCs with no run-time dependencies. It runs starts-up 4+ times faster than Moose according to its authors, at the expense of implementing only 96% a subset of its larger cousin's more superficial and sexier features.

It can be a nicer, gentler introduction to the world of Moose. Like the cervine form, the rodent provides a simple means of providing accessors which are more explicit than a generalized AUTOLOAD mechanism, while still eliminating redundant code. Plenty of other fancy OO features come along for the ride, but no "metaprotocol stuff," which some would argue is the raison d'être of Moose.

You may be willing to make such a trade-off, but what if you're not writing the code, and instead run into some other module that foists Moose upon you? That author may or may not need all of Moose, but chances are good they don't. Well, if they were kind enough to use Any::Moose instead, then the code will run with whichever is available, unless you force it by setting the currently undocumented $ENV{ANY_MOOSE} to the implementation of your choice.

What if they weren't aware of Any::Moose? You could edit their code (ick), make a local copy of Mouse and run find -type f | xargs perl -pi~ -e 's/Mouse/Moose/g' (very naughty), or get a little clever. With an updated version of Package::Alias that has not yet been accepted/released,2 it is possible to dress up your Mouse as a Moose.

Olive does not fly, and goes 'woof.'
Comet flies, and goes '.'

#%INC-luded files, nary a Moose in sight
$VAR1 = {
          '' => '/usr/lib/perl/5.10/',                                  
          '' => '/usr/share/perl/5.10/',
          '' => '/usr/lib/perl/5.10/',
          'Data/' => '/usr/lib/perl/5.10/Data/'
          '' => '/usr/share/perl/5.10/',                  
          'List/' => '/usr/lib/perl/5.10/List/',                  

          #Package::Alias keeping Moose from being loaded
          '' => '/usr/local/share/perl/5.10.0/',
          '' => '/usr/local/share/perl/5.10.0/',
          'Mouse/' => '/usr/local/share/perl/5.10.0/Mouse/',
          'Mouse/Meta/' => '/usr/local/share/perl/5.10.0/Mouse/Meta/',                                                                  
          'Mouse/Meta/' => '/usr/local/share/perl/5.10.0/Mouse/Meta/',                                                                          
          'Mouse/Meta/Method/' => '/usr/local/share/perl/5.10.0/Mouse/Meta/Method/',                                                      
          'Mouse/Meta/Method/' => '/usr/local/share/perl/5.10.0/Mouse/Meta/Method/',                                                
          'Mouse/Meta/Method/' => '/usr/local/share/perl/5.10.0/Mouse/Meta/Method/',
          'Mouse/Meta/' => '/usr/local/share/perl/5.10.0/Mouse/Meta/',
          'Mouse/Meta/' => '/usr/local/share/perl/5.10.0/Mouse/Meta/',
          'Mouse/Meta/' => '/usr/local/share/perl/5.10.0/Mouse/Meta/',
          'Mouse/' => '/usr/local/share/perl/5.10.0/Mouse/',  
          'Mouse/' => '/usr/local/share/perl/5.10.0/Mouse/',
          'Mouse/Util/' => '/usr/local/share/perl/5.10.0/Mouse/Util/',                                                      
          'Package/' => '/usr/local/share/perl/5.10.0/Package/',
          'Scalar/' => '/usr/lib/perl/5.10/Scalar/',              
          '' => '/usr/lib/perl/5.10/',
          '' => '/usr/share/perl/5.10/',
          '' => '/usr/share/perl/5.10/',
          '' => '/usr/share/perl/5.10/',                            
          '' => '/usr/share/perl/5.10/',
          '' => '/usr/share/perl/5.10/',
          '' => '/usr/share/perl/5.10/',
          '' => '/usr/share/perl/5.10/',                  
          'warnings/' => '/usr/share/perl/5.10/warnings/',

Unfortunately, because of the way Mouse is implemented, this magic comes at the cost of a little extra work, "Any::Moose::Forcefully". Lines 6 through 20 could easily be inlined in our code, but are placed into a separate module to make it clear that you only need the BEGIN block once in your code, as early as possible, and it will supercede any later calls to use Moose. The use of an explicit BEGIN for the class substitution clauses is key to enforce the proper order of module loading. All of these lines, except for number 7, are simply copied from Mouse itself, and amount to hardcoding a particular feature set.

Note that even if other code will compile correctly with Mouse, it's possible the code could be doing some deep introspection and you may end up with Chet rather than Comet. it is therefore recommended that you run the code's test suite against Mouse whether you force it through Package::Alias or substituting Any::Moose.

   1 package Reindeer;
   2 use Any::Moose::Forcefully; #All Mooses are Mouses
   3 use Moose;
   5 has 'name'  => (is=>'ro', isa=>'Str',  default=>'Comet');
   6 has 'sound' => (is=>'ro', isa=>'Str',  default=>'');
   7 has 'flies' => (is=>'ro', isa=>'Bool', default=>1);
   9 sub stats {
  10     my ($self) = @_;
  11     printf("%s %s, and goes '%s.'\n",
  12 	   $self->name(),
  13 	   ($self->flies ? 'flies' : 'does not fly'),
  14 	   $self->sound());
  15 }
  17 1;
  19 package main;
  20 use Data::Dumper;
  21 import Reindeer; #not use, package is in the same file
  23 my $Olive = Reindeer->new(flies=>0, name=>'Olive', sound=>'woof');
  24 my $Comet = Reindeer->new();
  26 $Olive->stats();
  27 $Comet->stats();
  29 print "\n", Dumper \%INC;

   1 package Any::Moose::Forcefully;
   2 BEGIN{
   3     use Package::Alias Moose => Mouse;
   5     #Make Mouse's finicky internal checks happy...
   6     Mouse::Exporter->setup_import_methods(
   7 					  exporting_package=>'Moose',
   9 					  #Alas the defaults live in Mouse...
  10 					  as_is => [qw(
  11 							extends with
  12 							has
  13 							before after around
  14 							override super
  15 							augment  inner
  16 						     ),
  17 						\&Scalar::Util::blessed,
  18 						    \&Carp::confess,
  19 						   ],
  20 					 );
  21     #Otherwise we get
  22     #The package Moose package does not use Mouse::Exporter at
  23     #  /usr/local/share/perl/5.10.0/Mouse/ line 140
  24     #    Mouse::Exporter::do_import('Moose') called at line 23
  25     #    Reindeer::BEGIN() called at line 23
  26     #    eval {...} called at line 23
  27     #BEGIN failed--compilation aborted at line 23.
  28 }
  30 1;

1. As determined by cloc and CPAN Tester dependencies.

2. I've submitted a patch to Package::Alias, which is also generally useful for robustly aliasing packages and classes of any sort to another name.

My suggestion that Any::Moose be patched to use Package::Alias magic to make it a more general solution has not been embraced, but someone is welcome to produce a proper Any::Moose::Forcefully/MooseX::Hijack::WithMouse.

As an alternative to adopting the currently unofficial implementation of Alias, and admittedly ugly Any::Moose::Forcefully or something similar, you could use the following snippet suggested by one of Moose's contributors:

sub Moose::import { goto &Mouse::import }
$INC{""} = 1;

See also Any::Moose::Convert, created 2009-12-04

View Source (POD)