Perl5’s object system is impressive in its simplicity. From that simplicity it gains an incredible flexibility which both matches Perl and allows for amazing things. However, it is easy to find patterns created from the spartan nature of the system. This last point has caused many people to design new object interfaces on top of Perl’s object system. The most robust and popular of these is Moose. This first tutorial will show you a simple way to create classes using Moose.
Moose allows the user to easily declare the attributes that are provided on a class along with metadata about those attributes. This metadata defines many important aspects of the attribute, such as what type of attribute it is, what the permissions are on the attribute, the default value of the attribute, and many other things. Here is an example of a simple class called employee:
This class definition does several things, so let’s break it down. First we use Moose, this exports some spiffy ‘keywords’ like has. Additionally, it very sneakily uses the strict and warnings pragma in our package which is very convenient. Next we use the has keyword to define an attribute called pay. We define this attribute as being both readable and writable through its accessor and we say that the accessor should only accept numbers. Next, we create another attribute called manager, which has an accessor that both allows reading and writing and only accepts Employee objects. How does it know that Employees are objects? Well, Moose has a list of things that it considers types. If the type is already defined then it uses that type, otherwise it assumes it is a class.
That seems like that could be dangerous though. For example, what if you typo’d ‘Num’ to be ‘num’ or ‘NUm’ or if you spelled out ‘number’? In these cases Moose would assume that ‘num’, ‘NUm’, or ‘number’ were names of classes and only accept values that were of that class. Don’t worry though, someone has already seen and solved that problem, the solution we will use for this is a combination of MooseX::Has::Sugar and MooseX::Types. MooseX::Has::Sugar provides us with several predefined ‘keywords’ that we can use and if we typo we’ll get compile time errors. Additionally, it defines rw and ro (the value for is that indicates readonly) in such a way that we no longer have to provide the ‘is =>’ portion. MooseX::Types allows us to predeclare types and organize them into libraries. Now our class definition looks like this:
Let’s try and use this class in a script:
Now, if we look at that script we see some very interesting things. We never explicitly created a new, manager, or salary method but they are all there. This is because Moose created each of these for us to use. In fact, it is imperative that when you are using Moose you do not create your own new method, that is very dangerous.
Of course, Moose provides many other meta attributes to define the attributes of your class, some of which are:
- required - whether calls to new() must include this attribute, can be 1 or 0
- default - the default value of this attribute or a reference to a sub that will return the default value, it is better to use builder though (see below)
- lazy - whether this attribute will be built when the object is built or whether Moose will wait until it is first used
- …and more we’ll cover in following weeks
We’ll rewrite our class by adding some attributes and adding these meta attributes to all the attributes, but this time we won’t use MooseX::Has::Sugar or MooseX::Types:
Builder vs. Default
I mentioned above that you can set the default value of an attribute with default however it is better to use builder. The builder option takes the name of a subroutine that will return a value for the attribute instead of a subref or actual value. The benefit of this is that if you inherit from a base class that uses a builder you can easily override the default value by overriding the sub that builds the value, with default it is not so simple.
There is one important caveat to mention. Neither Moose nor any of its extensions are magical and, in fact, none of them actually create new keywords. Instead, all of these modules are importing methods into your package just like any other tool. This means that they happen in the same order as you would expect function calls to happen. It also means you can write them any way you want, however this is a place where following the standard benefits everyone.
Dirty Little Secret
As much as I like the idea of MooseX::Has::Sugar and MooseX::Types I should probably mention that I actually rarely use them. However, they seem like a good tool to start off with.