dcsimg

The Moose is Flying, Part Two

The Moose object system enforces type, validates values, and coerces parameters to be the correct type.

Last month, I introduced the Moose object system by rewriting the sample code found in the perlboot man page to use Moose. Let’s continue the discussion and look at some of the features I didn’t cover last time.

Animal House

The role Animal included attributes name and color, and actions speak and eat. You can add a birthdate attribute to the Animal with:

has ’born’ => (is => ’ro’);

Since a birthdate isn’t mutable, it’s marked as read-only. By default, this attribute accepts any scalar, so these definitions 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]);

You can use the Moose type system to narrow the permissible type of the birthdate attribute:

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, a run-time error occurs if you try to put anything in the born attribute other than a DateTime. Hence…

my $horse = Horse->new(born => ’yesterday’, name => ’Newbie’);

… fails, but this succeeds:

my $horse = Horse->new(born => DateTime->now, name => ’Newbie’);

The DateTime string for isa refers here to the Perl class. However, you 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. A type is created by starting with any object, and requiring that the object meet the additional qualification of being a subclass of Perl’s DateTime.

At this point, you could continue to use this Moose-style DateTime type as you had before. But once you take the previous step, you can further subtype the type. For example, you can require that the birthdate be a historical date (a date in the past):

subtype ’HistoricalDateTime’
  => as ’DateTime’
  => where { $_ <= DateTime->now };
has ’born’ => (is => ’ro’, isa => ’HistoricalDateTime’);

Now, not just any DateTime suffices. The object has to represent a date 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 you could still use casual forms like yesterday and 3/14/1929. These are both understood by Date::Manip. You 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. The newbie horse is born yesterday, as you’d expect.

A Little Bit of Coercion

But it’d be nice to just drop yesterday into the slot and have the object do the of that for you. And with coercions, you can.

Since passing a simple string as the birth time is illegal, you can instruct Moose to 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. You permit this coercion 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 is used to convert the string into a DateTime object as your 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 remains in place, the date must be a historical date.

Besides the named classes and Str, Moose::Util::TypeConstraints also establishes types of things like Bool and HashRef. You can even have multiple coercions defined, as long as each coercion is distinct. For example, you could use a hashref in the birth time slot to indicate that key/value pairs are being passed to a DateTime constructor directly:

  coerce ’DateTime’
    => from ’HashRef’
    => via { DateTime->new(%$_) };

And now you 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 you specify for a default is subject to the same coercions and type checking. So, you can update born as:

has ’born’ => (is => ’ro’,
     isa => ’HistoricalDateTime’,
     coerce => 1,
     default => ’yesterday’,
);

And now the animals default to being” born yesterday”. The default value is still subject to the type constraints, so if you replace yesterday with tomorrow, the default value is rejected. Coercion happens as each object is created, so a default value of one minute ago produces a new time each time it is called.

The Joys of Being Lazy

Another interesting attributes 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. I chatted with the author of Moose online on IRC and got the error fixed before I could turn this article in. Yay!

Anything built with Moose has a very high degree of introspection. For example, you can ask an animal friend for its meta object, with which you can make further requests:

my $horse = Horse->new;
my $meta = $horse->meta; # or equivalently, Horse->meta

$meta is a Moose::Meta::Class. You can ask the horse for its roles:

my @roles = @{$meta->roles};

In this case, there’s one role (of type Moose::Meta::Role, and you can get the name with:

map { $_->name } @roles; # qw(Animal)

It produces Animal, as expected. You can also ask the meta object for all applicable methods:

my @methods = $meta->get_method_list;

This returns:

BEGIN
born
color
default_color
eat
meta
name
private_set_color
sound
speak

That’s very nice. (I’m not sure what BEGIN is doing in there, but the rest have been defined.) Many of these methods relate to the attributes; you 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. You can map them through name as before to get the names:

map { $_->name } @attrs; # qw(born color name)

You can also see if each attribute has setters:

grep { $_->has_writer } @attrs; # qw(color)

Note that only color has a setter; the other two are read-only.

Moose Not Mickey Mouse Code

As noted above, Moose is in active development, 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 plug-ins, 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 plug-ins 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 worthy of their own descriptions later.

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!

Fatal error: Call to undefined function aa_author_bios() in /opt/apache/dms/b2b/linux-mag.com/site/www/htdocs/wp-content/themes/linuxmag/single.php on line 62