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!


Randal L. Schwartz is a renowned expert on the Perl programming language (the lifeblood of the Internet), having contributed to a dozen top-selling books on the subject, and over 200 magazine articles. Schwartz runs a Perl training and consulting company (Stonehenge Consulting Services, Inc of Portland, Oregon), and is a highly sought-after speaker for his masterful stage combination of technical skill, comedic timing, and crowd rapport. And he's a pretty good Karaoke singer, winning contests regularly.

Schwartz can be reached for comment at merlyn@stonehenge.com or +1 503 777-0095, and welcomes questions on Perl and other related topics.