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 95 (Jul 2007)
[suggested title: ``The Moose is Flying (part 2)'']
Last month, I introduced the Moose object system by walking through
the code developed in the perlboot manpage, rewriting it to use
Moose. Continuing from that discussion, let's look at some of the
features I didn't cover last month.
Our Animal role included attributes of name, and color,
and actions of speak and eat. We can add a birthdate attribute
to the mix with:
has 'born' => (is => 'ro');
As we don't want to be able to change the birthdate, we'll make it read-only. By default, this attribute accepts any scalar, so these are all equally valid:
my $horse = Horse->new(born => 'yesterday', name => 'Newbie'); my $cow = Cow->new(born => 'spring of 82', name => 'Bessie'); my $mouse = Mouse->new(name => 'Minnie', born => '3/14/1929'); my $racehorse = RaceHorse->new(name => 'Slew', born => [3, 5, 59]);
We can get a bit of help from Moose to narrow down the permissible type of this new birthdate attribute using Moose's typing system:
require DateTime; has 'born' => (is => 'ro', isa => 'DateTime');
The isa parameter here declares that the born parameter must be
a DateTime object, or at least something that responds true to
UNIVERSAL::isa($thing, "DateTime").
Now, if we try to put anything in this born attribute other than
a DateTime, we get a run-time error. So, this fails:
my $horse = Horse->new(born => 'yesterday', name => 'Newbie');
But this succeeds:
my $horse = Horse->new(born => DateTime->now, name => 'Newbie');
The DateTime string for isa refers here to the Perl class.
However, we can also define this as an artificial Moose type:
use Moose::Util::TypeConstraints;
require DateTime;
subtype 'DateTime'
=> as 'Object'
=> where { $_->isa('DateTime') };
This works like before, but now identifies DateTime as a
Moose-type. The type is created by starting with any Object, and
then requiring that this object pass the additional qualification of
being a subclass of Perl's DateTime.
At this point, we could continue to use this Moose-style DateTime type as we had before. But once we've done this, we can further subtype the type. For example, we can require that the date be a historical date (before now):
subtype 'HistoricalDateTime'
=> as 'DateTime'
=> where { $_ <= DateTime->now };
has 'born' => (is => 'ro', isa => 'HistoricalDateTime');
Now, not just any DateTime will do. It has to be something that isn't
in the future. The expression in where can be any expression
returning a true/false value, using $_ as a proxy for the object in
question.
It'd be easier if we could still use casual forms like yesterday
and 3/14/1929. These are both understood by Date::Manip. We
could parse the strings with Date::Manip, then pull out the component
values and hand them to DateTime, but luckily, there's already
a module to do that: DateTime::Format::DateManip.
use DateTime::Format::DateManip;
my $yesterday = DateTime::Format::DateManip->parse_datetime('yesterday');
my $newbie = Horse->new(born => $yesterday, name => 'Newbie');
Not bad. Our newbie horse is born yesterday, as we expect. But
it'd be nice to just drop yesterday into the slot and have it do
all of that for us. And with coercions, we can.
Since passing a simple string as the birthtime is illegal, we can
inform Moose that it should take that string and run it through
DateTime::Format::DateManip automatically:
coerce 'HistoricalDateTime'
=> from 'Str'
=> via {
require DateTime::Format::DateManip;
DateTime::Format::DateManip->parse_datetime($_);
};
The via code block takes $_ as the input value, which is
expected to be a string (Str). The last expression evaluated in
the code is the new HistoricalDateTime value. We then permit this
coercion to be used by adding coerce into the attribute
declaration:
has 'born' => (is => 'ro',
isa => 'HistoricalDateTime',
coerce => 1,
);
Now, the born slot accepts either an explicit DateTime as
before, or a simple string. The string must be acceptable to
Date::Manip, which will be used to convert the string into a
DateTime object as our object is created.
my $newbie = Horse->new(born => 'yesterday', name => 'Newbie'); my $mouse = Mouse->new(name => 'Minnie', born => '3/14/1929');
Also, because the type constraint is still in place, the verification to ensure the result is a historical date is still active.
Besides the named classes and Str, Moose::Util::TypeConstraints
also establishes types of things like Bool and HashRef.
We could even have multiple coercions defined, as long as they are
distinct. For example, we could use a hashref in the birthtime slot
to indicate that we are passing key/value pairs to be handed to a
DateTime constructor directly:
coerce 'DateTime'
=> from 'HashRef'
=> via { DateTime->new(%$_) };
And now we can use a hashref to define the birthdate:
my $mouse = Mouse->new(
name => 'Minnie',
born => { month => 3, day => 14, year => 1929 },
);
If the value for born is a DateTime, it's used directly. If
it's a string, it's passed to DateTime::Format::DateManip. And if
it's a hashref, it's passed directly as a flattened list to the
DateTime constructor. Very cool.
The value we specify for a default is subject to the same coercions
and type checking. We can update born as:
has 'born' => (is => 'ro',
isa => 'HistoricalDateTime',
coerce => 1,
default => 'yesterday',
);
And now our animals default to being ``born yesterday''. Note that the
default value is still subject to the type constraints, so if we
replace yesterday with tomorrow, the default value will be
rejected properly. Note that the coercion happens as each object is
created, so a default value of one minute ago will give us a new
time each time it is called.
While we're on things we can do to attributes, another interesting
item is lazy. If the default value is expensive to compute, we can
say ``don't really do this until you need to do it''. For example,
turning yesterday into a DateTime is slightly expensive, so we
can flag that as lazy:
has 'born' => (is => 'ro',
isa => 'HistoricalDateTime',
coerce => 1,
default => 'yesterday',
lazy => 1,
);
Speaking of great support, as I was writing that last paragraph, I noticed a bug in the result, and chatting with Stevan on IRC got it fixed before I could turn this article in. Yeay.
Anything built with Moose has a very high degree of introspection
available. For example, we can ask one of our animal friends to give us
the meta object, with which we can make further requests:
my $horse = Horse->new; my $meta = $horse->meta; # or equivalently, Horse->meta
$meta is a Moose::Meta::Class. We can ask the horse for the
roles:
my @roles = @{$meta->roles};
In this case, we see that we've mixed in one role (of type Moose::Meta::Role,
and get the name with:
map { $_->name } @roles; # qw(Animal)
which gives us Animal as we might expect. We can also ask
the meta object for all applicable methods:
my @methods = $meta->get_method_list;
which returns
BEGIN born color default_color eat meta name private_set_color sound speak
Nice. Not sure what BEGIN is doing in there, but the rest are things
that we've defined. Many of these methods relate to the attributes,
but we'd can query those definitively using
compute_all_applicable_attributes instead:
my @attrs = $meta->compute_all_applicable_attributes;
The result is a set of Moose::Meta::Attribute objects. We can
map them through name as before to get the names:
map { $_->name } @attrs; # qw(born color name)
We can also see if they have setters:
grep { $_->has_writer } @attrs; # qw(color)
Note that only color has a setter: the other two are read-only,
so this makes sense.
As noted above, Moose is still being actively developed, but is
production ready as long as you stick with things that work.
You'll find the latest Moose in the CPAN, along with some other
core plugins, typically in the MooseX namespace.
For example, MooseX::Getopt allows you to define your @ARGV processing
using type coercion, type constraints, and all that jazz. I haven't had time
to play with that yet, but it's on my to-do list, so perhaps I'll cover that
in a future column.
Similarly, MooseX::Object::Pluggable makes it easy to write classes
that are pluggable, meaning that they can work well with plugins
that define additional methods and attributes. (Think of a generic
web server or IRC bot object that has individually selectable
additional behaviors.) Again, I'm just noticing these, and they look
like they are worthy of their own descriptions later.
Also note that Moose is itself built on Class::MOP, which is a
framework for making class frameworks. Perhaps other projects besides
Moose will be using Class::MOP as a starting point as well. For
example, the infrastructure of Class::Prototyped (which I use in my
own CGI::Prototype) might be built on Class::MOP, giving it more
flexibility and robustness.
I hope you've enjoyed this two-part introduction to Moose. Have
fun playing with a production-ready flexible object-building system. Until next time, enjoy!

