Moose: A postmodern object system for Perl 5

[ Perl tips index ]
[ Subscribe to Perl tips ]

Moose is a complete object system for Perl 5. It provides a simple and consistent syntax for attribute declaration, object construction and inheritance, without the need to understand how those things are implemented. Furthermore, Moose allows you to separate the use of your data from its underlying representation, thus you need not know whether Moose is using a hash, array or something else in which to store your data. This allows the programmer to focus on ``what'' the code is doing, rather than ``how''.

The basics

Using Moose gives us more easy accessors, clever classes, roles, types and coercions and loads of other cool stuff. However the basic OO rules still apply.

A class is just a package

To create a class in Perl we create the package and use the Moose module:

        package PlayingCard;
        use Moose;

        # we now have a Moose-based class
        1;

That's it! Moose also turns on strict and warnings for us, so we don't have to do that ourselves, although nothing bad happens if we turn them on explicitly.

Classes in Moose can be used straight away, as it establishes a constructor for us automatically. Admittedly our class isn't very useful yet, but we can still create simple objects if desired:

        use PlayingCard;

        my $card = PlayingCard->new();          # works!

If you need your constructor to do more than just create an object with the given attributes then Moose provides some handy construction hooks: BUILDARGS and BUILD. To read more about these read Moose::Manual::Construction.

Methods are just subroutines

Just as in regular OO Perl, we add subroutines to our class in order to define object methods:

        package PlayingCard;
        use Moose;

        sub show_card {
                my $self = shift;

                ....
        }

        1;

We then call them on our object, just as we did before:

        use PlayingCard;

        my $card = PlayingCard->new();          # works!

        # Print out the card's value
        print $card->show_card();

Attributes, basic types and accessors

One of the huge improvements that Moose provides is an easy and straight-forward way to handle accessors. We declare these with an English-like syntax and Moose handles everything else.

        # Create our class (this also creates our constructor)
        package PlayingCard;
        use Moose;

        # Add the suit attribute
        has 'suit' => (
                is  => 'ro',    # Once card is created, suit won't change
                isa => 'Str',   # Suits will be strings
                required => 1,  # This attribute must be defined
        );

        # Add the value attribute
        has 'value' => (
                is  => 'ro',    # Once card is created, value won't change
                isa => 'Str',   # Values will be strings
                required => 1,  # This attribute must be defined
        );

        # Returns the card name
        sub show_card {
                my $self = shift;

                return $self->value() . " of " . $self->suit();
        }

	__PACKAGE__->meta->make_immutable;     # explained below
        no Moose;       # turn off Moose-specific scaffolding

        1;

Once we have created these, we now have our full PlayingCard object. We can make a whole bunch of cards, show their values, put them in a deck or whatever:

        use PlayingCard;

        my $card1 = PlayingCard->new( 
                value => "ace",
                suit  => "hearts",
        );

        my $card2 = PlayingCard->new(
                value => "king",
                suit  => "spades",
        );

        print $card1->show_card();
        print $card2->show_card();

        # trying to change a value is not allowed
        $card1->suit("diamonds");       # ERROR

Read-write or read-only

When we declare an attribute we must specify whether whether it is read-only (ro) or read-write (rw). Failing to do this will not cause a warning, but will result in the attribute not being usable which is a hard bug to track down. An attribute which is read-only can only be set when the object is created, while attributes which are read-write may have their value changed as required.

        has 'suit' => (

                # Suit is read-only

                is  => 'ro',
                isa => 'Str',
        );

In the case of our card, it doesn't make sense for a card to change either its suit or its value after creation time. However, in a game where cards can be face-up or face-down, it does make sense for these to change:

        has 'visible' => (

                # visible is read-write
                is  => 'rw',
                isa => 'Bool',
        );

Don't forget to specify whether your attribute is read-only or read-write.

Types

Moose provides a number of basic attribute types which we can use for our class attributes. These form a simple hierarchy, where any more specialised type may be used in the place of a more generalised type (for example you can use an integer (Int) instead of a number (Num) and either can be used where a string is expected. You can see this hierarchy by reading perldoc Moose::Util::TypeConstraints and it is also reproduced below:

  Any
  Item
      Bool
      Maybe[`a]
      Undef
      Defined
          Value
              Str
                  Num
                      Int
                  ClassName
                  RoleName
          Ref
              ScalarRef
              ArrayRef[`a]
              HashRef[`a]
              CodeRef
              RegexpRef
              GlobRef
                  FileHandle
              Object

Any type followed by a type parameter ['a] can be parameterised. Thus you can write:

  ArrayRef[Int]    # an array of integers
  HashRef[CodeRef] # a hash of str to CODE ref mappings
  Maybe[Str]       # value may be a string, may be undefined

You can also create your own types and constraints, which we will cover later.

These are not a strong typing system for Perl 5. These are constraints to ensure that attributes for Moose-based objects contain what you expect them to.

Accessors

Once you have declared your attributes in your class, you automatically receive accessor functions for them. These are named by the attribute name and if your attribute is marked as read-write (rw) then you can use them to change the value as well:

        my $card = PlayingCard->new(
                value      => "two",
                suit       => "clubs",
                decoration => "flowers",
        );

        print $card->suit();            # prints "clubs";

        $card->decoration("money");     # decoration is now money

You can tell Moose to name your accessors differently if you want:

        has 'decoration' => (
                is     => 'rw',
                isa    => 'Str',
                reader => 'get_decoration',
                writer => 'set_decoration',
        );

Then later we call these as required:

        $card->set_decoration("money"); # decoration is now money
        print $card->get_decoration();  # prints "money"

Moose will still check the type constraints and run any triggers with methods named like this.

make_immutable

At the end of our PlayingCard class, above, we had the following line:

	__PACKAGE__->meta->make_immutable;

Calling make_immutable makes Moose faster as your class' object construction and destruction becomes inlined and no longer invokes the meta API. It also declares that you do not intend to make any further changes to the class via the metaclass API (which is what gives us all the cool Moose features). This is why we do this at the end of our class declaration. In essence this means that we cannot modify our class during the run-time of the calling program, which is a rare thing to want to do, and thus a small price to pay for the speed increase.

BUILDARGS and BUILD

For many objects, we would like to run code when an object is created. This may be to attach to a resource, run validation checks, or merely fill in some sensible defaults. Moose allows us to do this by defining our own BUILDARGS and BUILD methods.

The BUILDARGS method, if defined, is executed before the object is constructed. It's useful for tweaking our arguments before they hit Moose. One very common use of BUILDARGS is to support object construction with a non-hash syntax. For example, we may allow our playing cards to be constructed with a single argument (eg: 'Kh' for the king of hearts).

        around BUILDARGS => sub {
            my ($orig, $class, @args) = @_;

            # $orig is Moose's (or our parent's) BUILDARGS
            # subroutine.  This allows us chain together
            # classes that all do their own argument handling.

            if ( @args == 1 and ! ref $args[0] ) {

                # If we only have one argument, and it's not a
                # reference, then extract our card information.

                my ($value, $suit) = ($args[0] =~ /^(\w)(\w)$/);

                return $class->$orig(
                    value => $value,
                    suit  => $suit,
                );

            }
            else {
                return $class->$orig(@args);
            }
        };

The use of the around keyword will be explained more in another tip.

The BUILD method is called immediately after an object is constructed. It's often useful to perform extra validation steps, connect to a database, open a file, or otherwise do work that needs to occur at object construction. For example, in the game of Pontoon, tens are removed from the deck.

        package PlayingCard::Pontoon;
        use Moose;
        extends 'PlayingCard';

        sub BUILD {
            my ($self) = @_;

            if ($self->value eq 'T') {
                die "Tens are not allowed in Pontoon\n";
            }
        }

There is no need to call parental BUILD methods; in fact, it's a grave mistake to do so. Moose ensures that all BUILD methods are called. Parental BUILD methods are guaranteed to be called before their children.

Further reading

[ Perl tips index ]
[ Subscribe to Perl tips ]


This Perl tip and associated text is copyright Perl Training Australia. You may freely distribute this text so long as it is distributed in full with this Copyright noticed attached.

If you have any questions please don't hesitate to contact us:

Email: contact@perltraining.com.au
Phone: 03 9354 6001 (Australia)
International: +61 3 9354 6001

Valid XHTML 1.0 Valid CSS