Copyright Notice
This text is copyright by InfoStrada Communications, Inc., and is used with their permission. Further distribution or use is not permitted.This text has appeared in an edited form in Linux Magazine magazine. However, the version you are reading here is as the author originally submitted the article for publication, not after their editors applied their creativity.
Please read all the information in the table of contents before using this article.
Linux Magazine Column 94 (Jun 2007)
[suggested title: ``The Moose is Flying (part 1)'']
Perl's object system is very ``flexible'', meaning, you get to build it from the ground up. You can build traditional hash-based objects, or more exotic array-based or inside-out objects. And then you have to create the accessors, define access policies, and perform a lot of repetitive code.
Luckily, Perl is introspective enough that you can get Perl to do most
of the hard boring work. This has resulted in a number of ``class
frameworks'' finding their way onto the CPAN. The Moose
framework
appeared about a year ago, and I initially dismissed it as ``yet
another class framework'', much in the same way as I feel about yet
another templating system or object-relational mapper.
However, I recently took a look at what Moose
had become, and was
pleasantly surprised. As I started playing with it, I exclaimed
frequently that this framework would have saved me quite a bit of time
on some past projects, such as the text I wrote for our Intermediate
Perl course and book, parts of which have been included as the
perlboot manpage in the distribution. Let's recreate the ``animal''
classes from that text, using Moose
, to see how this emerging
framework simplifies things.
First, we'll create a horse class in Horse.pm
that has a name and a color:
package Horse; use Moose; has 'name' => (is => 'rw'); has 'color' => (is => 'rw'); 1;
Bringing in Moose
defines has
, which takes the name of an
attribute along with its properties. Here, we're saying that the
two attributes are ``read/write''. We can now use this class:
use Horse; my $talking = Horse->new(name => "Mr. Ed"); print $talking->name; # prints Mr. Ed $talking->color("grey"); # sets the color
Note that I didn't have to define a new
method: Moose does that for
me.
Now in the original text, Horse
inherited from Animal
. We can do that
rather simply. In Animal.pm
, we place:
package Animal; use Moose; has 'name' => (is => 'rw'); has 'color' => (is => 'rw'); 1;
And then update our Horse.pm
:
package Horse; use Moose; extends 'Animal'; 1;
Note that extends
here replaces the traditional use base
and
completely sets @ISA
, rather than adding to it. (It's possible
that you might want to put this inside a BEGIN block, although I've
not seen any examples requiring it yet.)
At this point, Horse
and Animal
are identical. They can both be
instantiated, and have the two given attributes. In the original
example, what distinguished a horse was the sound it made, which we
can add here:
package Horse; use Moose; extends 'Animal'; sub sound { 'neigh' } 1;
and then reference that in the common speak
method in Animal
:
package Animal; use Moose; has 'name' => (is => 'rw'); has 'color' => (is => 'rw'); sub speak { my $self = shift; print $self->name, " goes ", $self->sound, "\n"; } sub sound { confess shift, " should have defined sound!" } 1;
Note the use of confess
, another freebie from Moose. If the
derived class hasn't defined a sound, I want to complain. But since
Horse
defines sound
, I'll never see that for a horse. With this
code, I can create my classic talking horse:
my $talking = Horse->new(name => 'Mr. Ed'); $talking->speak; # says "Mr. Ed goes neigh"
So far, I'm still coding things that would be simple without Moose, so
let's start diverging a bit to see the full power. First, an
Animal
is really an abstract class, being used only to provide
common attributes and methods to a concrete class (in this case, the
horse class). In Moose-terminology, this can best be described as a
role. A role is like a mix-in, providing a collection of
attributes and methods that use those attributes. A role never has
any instances, because it's not a complete class.
When we make Animal
a role, we'll also get some additional support:
package Animal; use Moose::Role; has 'name' => (is => 'rw'); has 'color' => (is => 'rw'); sub speak { my $self = shift; print $self->name, " goes ", $self->sound, "\n"; } requires 'sound'; 1;
Note that we've replaced the confess-including stub with requires
.
This informs Moose that this role must now be used with a class that
provides the sound
method, which will be checked at compile-time.
To pull in a role, we use with
rather than extends
:
package Horse; use Moose; with 'Animal'; sub sound { 'neigh' } 1;
Had we failed to include sound
, we'd get notification very early
on. Cool. At this point, Horse
otherwise still works as before.
What about those with
and requires
keywords? Because they're
defined by the Moose
and Moose::Role
imports, they remain as
part of the package. For the purists in us who don't like that kind
of pollution, we can throw them away when we're done, using the
correponding no
keyword (similiar to use strict
and no
strict
). For example, we'd clean up Horse.pm
with:
package Horse; use Moose; with 'Animal'; sub sound { 'neigh' } no Moose; # gets rid of scaffolding 1;
And similarly, Animal.pm
requires no Moose::Role
at the end.
Moose supports the notion of a default value. Let's add in the default color, and make that a class responsibility as well:
package Animal; ... has 'color' => (is => 'rw', default => sub { shift->default_color }); requires 'default_color'; ...
If the color isn't provided, the default color of the class will be
consulted, and requires
ensures that the concrete class provides
this default color. Our derived animal classes now look like:
## Cow.pm: package Cow; use Moose; with 'Animal'; sub default_color { 'spotted' } sub sound { 'moooooo' } no Moose; 1; ## Horse.pm: package Horse; use Moose; with 'Animal'; sub default_color { 'brown' } sub sound { 'neigh' } no Moose; 1; ## Sheep.pm: package Sheep; use Moose; with 'Animal'; sub default_color { 'black' } sub sound { 'baaaah' } no Moose; 1;
Now we can count sheep as one of our implemented classes:
use Sheep; my $baab = Sheep->new(color => 'white', name => 'Baab'); $baab->speak; # prints "Baab goes baaaah"
Well, this is pretty straightforward. Let's solve a few other problems from the original material.
The Mouse
class was special, because it extended the speak
method with an additional line of output. While we could use
traditional SUPER::
-based method calls to call parent-class
behaviors, this doesn't work with roles. (Roles don't end up in
@ISA
, because they're ``glued in'' rather than ``tacked above''.)
Luckily, Moose provides the convenient after
call to append
additional steps to an existing subroutine. Moose does this by
replacing the original subroutine with a new subroutine that calls the
original routine and then calls the additional code. The context
(list, scalar, or void) is properly preserved, as is the original
return value. Our amended speak
looks something like:
package Mouse; use Moose; with 'Animal'; sub default_color { 'white' } sub sound { 'squeak' } after 'speak' => sub { print "[but you can barely hear it!]\n"; }; no Moose; 1;
This gives us a properly functioning mouse:
my $mickey = Mouse->new(name => 'Mickey'); $mickey->speak;
which results in:
Mickey goes squeak [but you can barely hear it!]
We can also use before
and around
to precede the original
behavior or control the calling of the original behavior, as
necessary. For example, to allow name
to be used as both an accessor
and still return an unnamed Horse
when used as a class method, we
can ``around'' the resulting name accessor:
package Animal; ... has 'name' => (is => 'rw'); around 'name' => sub { my $next = shift; my $self = shift; blessed $self ? $self->$next(@_) : "an unnamed $self"; };
The has
creates the original behavior. The around
intercepts
the original subroutine name, causing the original coderef to be
passed as the first parameter to this new subroutine, which we capture
in $next
. The original $self
is shifted away, and tested to see
if it's an object or not, via blessed
(conveniently exported via
Moose). For a blessed object, we get the original behavior (a getter
or setter), but for a class, we'll get the literal string.
What if we never gave our animal a name? We'll get warnings about undefined values. We can give a default name just as we did a default color:
has 'name' => ( is => 'rw', default => sub { 'an unnamed ' . ref shift }, );
Again, we'd want that around
immediately following this step.
If we don't want people setting the color after the initial instance creation, we can declare the attribute as read-only:
has 'color' => (is => 'ro', default => sub { shift->default_color });
Now an attempt to set the color is aborted with Cannot assign a
value to a read-only accessor...
. If we really wanted to have a way
to occasionally set the color, we can define a separately named
writer:
has 'color' => ( is => 'ro', writer => 'private_set_color', default => sub { shift->default_color }, );
Thus, we can't change the color of a mouse directly:
my $m = Mouse->new; my $color = $m->color; # gets the color $m->color('green'); # DIES
But we can use our private name instead:
$m->private_set_color('green'); # sets the color to green
By using a long name, we're less likely to accidentally call it, except where we intentionally want to change the color.
Let's create a RaceHorse
by adding ``race features'' to a Horse
.
First, we define the ``race features'' as, yes, another role:
package Racer; use Moose::Role; has $_ => (is => 'rw', default => 0) foreach qw(wins places shows losses); no Moose::Role; 1;
Note that since has
is just a subroutine call, we can use
traditional Perl control structures (here, a foreach
loop). With a
bit of code, we've added another four attributes. The initial value
of 0
means we don't have to write separate initialization code in
our constructor. Next, we can add some accessors:
package Racer; ... sub won { my $self = shift; $self->wins($self->wins + 1) } sub placed { my $self = shift; $self->places($self->places + 1) } sub showed { my $self = shift; $self->shows($self->shows + 1) } sub lost { my $self = shift; $self->losses($self->losses + 1) }
sub standings { my $self = shift; join ", ", map { $self->$_ . " $_" } qw(wins places shows losses); } ...
Each call to won
increments the number of wins. This would
be simpler if we presumed that these objects are implemented
as hashes (which they are by default), as:
sub won { shift->{wins}++; }
However, by using the public interface (a method call), we could change the implementation later to inside-out objects or array-based objects without breaking this code. This is especially important when creating a generic role, which could be mixed in to any kind of object.
To create the race horse, we just mix a horse with a racer:
package RaceHorse; use Moose; extends 'Horse'; with 'Racer'; no Moose; 1;
And now we can ride the ponies:
use RaceHorse; my $s = RaceHorse->new(name => 'Seattle Slew'); $s->won; $s->won; $s->won; $s->placed; $s->lost; # run some races print $s->standings, "\n"; # 3 wins, 1 places, 0 shows, 1 losses
So far, I've just scratched the surface of what Moose
provides.
Next month, I'll look at some of the more advanced features of
Moose
that help keep the complex things relatively simple.
Until then, enjoy!