Moose is a more complete object system for Perl 5. It builds on the existing Perl 5 system, and provides simpler defaults, better integration, and advanced features from several other languages, including Smalltalk, Common Lisp, and Perl 6. It's still worth learning the default Perl 5 object system, if only to write very simple programs where Moose is inappropriate or to maintain legacy code, but Moose is currently the best way to write object-oriented code in modern Perl 5.
Object orientation, or object oriented programming, is a way of managing programs by categorizing their components into discrete, unique entities. These are objects. In Moose terms, each object is an instance of a class, which serves as a template to describe any data the object contains as well as its specific behaviors.
A class in Perl 5 is part of a package, which provides a namespace in which to store class data.
Classes can be anonymous. See perldoc Moose::Meta::Class
.
This Cat
class appears to do nothing, but Moose does a lot of work to define the class and register it with Perl. With that done, you can create objects (or instances) of the Cat
class:
The arrow syntax should look familiar. Just as an arrow dereferences a reference, an arrow calls a method on an object or class.
A method is a function associated with a class. It resembles a fully-qualified function call in a superficial sense, but it differs in two important ways. First, a method call always has an invocant on which to perform the method. In the case of creating the two Cat
objects, the name of the class (Cat
) is the invocant:
Second, a method call always involves a dispatch strategy. The dispatch strategy describes how the object system decides which method to call. This may seem obvious when there's only a Cat
, but method dispatch is fundamental to the design of object systems.
The invocant of a method in Perl 5 method is its first argument. For example, the Cat
class could have a meow()
method:
Now all Cat
instances can wake you up in the morning because they haven't eaten yet:
By convention, invocants in Perl methods are lexical variables named $self
, but this is merely pervasive convention. The example implementation of meow()
does not use the invocant, so it's irrelevant once method dispatch has completed. In that sense, meow()
is like new()
; you can safely use the name of the class (Cat
) as its invocant. This is a class method:
Every object in Perl 5 is unique. Objects can contain attributes, or private data associated with each object. You may also hear this described as instance data or state.
To define object attributes, describe them as part of the class:
In English, that line of code means "Cat
objects have a name
attribute. It's readable but not writable, and it's a string." That single line of code creates an accessor method (name()
) and allows you to pass a name
parameter to the constructor:
Attributes do not need to have types, in which case Moose will skip all of the verification and validation for you:
This can be more flexible, but it can also lead to strange errors if someone tries to provide invalid data for an attribute. The balance between flexibility and correctness depends on your local coding standards and the type of errors you want to catch.
If you mark an attribute as readable and writable (with is => rw
), Moose will create a mutator method--a method you can use to change the value of an attribute:
Trying to use a ro
accessor as a mutator will throw an exception:
Now that individual objects can have their own instance data, the value of object orientation may be more obvious. An object is a bookmark for the data it contains as well as the behavior appropriate to that data. An object is a collection of named data and behaviors. A class is the description of the data and behaviors that instances of that class possess.
Moose allows you to declare which attributes class instances possess as well as how to treat those attributes. The examples shown so far do not describe how to store those instances. This information is available if you really need it, but the declarative approach can actually improve your programs. In this way, Moose encourages encapsulation, or hiding the internal details of an object from external uses of that object.
Consider a change to the age of a Cat
; instead of requesting that directly from the constructor, calculate the age of a Cat
based on the year of its birth:
While the syntax for creating Cat
objects has changed, the syntax for using Cat
objects has not. The age()
method does the same thing it has always done, at least as far as all code outside of the Cat
class understands. How it does that has changed, but that is a detail internal to the Cat
class--encapsulated within that class itself.
This new approach to calculating Cat
ages has another advantage; you can use default attribute values to reduce the code necessary to create a Cat
object:
The default
keyword on an attribute takes a function reference which returns the default value for that attribute when constructing a new object. If the constructor does not receive an appropriate value for that attribute, the object gets that default value instead. Now you can create a kitten:
... and that kitten will have an age of 0
until next year.
A program which deals with one type of data and one type of behavior on that data benefits little from the use of objects. A well-designed object-oriented program should be capable of managing many types of data. If done well, designing classes helps encapsulate specific details of objects into a single place), then a curious thing happens for the rest of the program: it has the opportunity to become less specific.
In other words, moving the specifics of the details of what the program knows about individual Cat
s (the attributes) and what the program knows that Cat
s can do (the methods) into the Cat
class means that code that deals with Cat
instances can happily ignore how Cat
does what it does.
This is clearer with an example. Consider this function, which takes an object and describes it:
It's obvious (in context) that you can pass a Cat
object to this function and get sane results. It's less obvious that you can pass other types of objects and get sane results. This is an important object orientation property called polymorphism, where you can substitute an object of one class for an object of another class if they provide the same external interface in the same way.
Any object of any class which provides the name()
, age()
, and diet()
accessors will work with this function. The function is sufficiently generic that any object which respects this interface is a valid parameter.
The benefit of the genericity in show_vital_stats()
is that neither the specific type nor the implementation of the object provided matters. Any invocant is valid if it supports three methods, name()
, age()
, and diet()
which take no arguments and each return something which can concatenate in a string context. You may have a hundred different classes in your code, none of which have any obvious relationship between each other, but if they conform to this expectation of behavior, they will work with this method.
This is an improvement over writing specific functions to extract and display this information for even a fraction of those hundred classes. This genericity requires less code, and using a well-defined interface as the mechanism to access this information means that any of those hundred classes can calculate that information in any way possible. The details of those calculations is where it matters most: in the bodies of the methods in the classes themselves.
Of course, the mere existence of a method called name()
or age()
does not by itself imply the behavior of that object. A Dog
object may have an age()
which is an accessor such that you can discover $rodney
is 8 but $lucky
is 3. A Cheese
object may have an age()
method that lets you determine how long to stow $cheddar
so that it becomes sharp; in other words, age()
may be an accessor in one class but not in another:
Sometimes it's useful to know what an object does. In other words, you need to understand its type.
A role is a named collection of behavior and state. A class is like a role, with the vital difference that you can instantiate a class, but not a role. While a class is primarily a mechanism for organizing behaviors and state into a template for objects, a role is primarily a mechanism for organizing behaviors and state into a named collection.
In short, a role is something a class does.
The difference between some sort of Animal
--with a name()
, an age()
, and a preferred diet()
--and Cheese
--which can age()
in storage--may be that the Animal
performs a LivingBeing
role, while the Cheese
performs a Storable
role.
While you could check that every object passed into show_vital_stats()
is an instance of Animal
, you lose some genericity that way. Instead, check that the object performs the LivingBeing
role:
Anything which performs this role must supply the name()
, age()
, and diet()
methods. This does not happen automatically; the Cat
class must explicitly mark that it performs the role:
That single line has two functions. First, it tells Moose to note that the class performs the named role. Second, it composes the role into the class. This process checks that the class somehow provides all of the required methods and all of the required attributes without potential collisions.
The Cat
class provides name()
and diet()
methods as accessors to named attributes. It also declares its own age()
method.
Now all Cat
instances will return true when queried if they provide the LivingBeing
role and Cheese
objects should not:
This design approach may seem like extra bookkeeping, but it separates the capabilities of classes and objects from the implementation of those classes and objects. The special behavior of the Cat
class, where it stores the birth year of the animal and calculates the age directly, could itself be a role:
Moving this code out of the Cat
class into a separate role makes it available to other classes. Now Cat
can perform both roles:
The implementation of the age()
method supplied by the CalculateAgeFromBirthYear
satisfies the requirement of the LivingBeing
role, and the composition succeeds. Checking that objects perform the LivingBeing
role remains unchanged, regardless of how objects perform this role. A class could choose to provide its own age()
method or obtain it from another role; that doesn't matter. All that matters is that it contains one. This is allomorphism.
Another feature of Perl 5's object system is inheritance, where one class specializes another. This establishes a relationship between the two classes, where the child inherits attributes and behavior of the parent. As with two classes which provide the same role, you may substitute a child class for its parent. In one sense, a subclass provides the role implied by the existence of its parent class.
Consider a LightSource
class which provides two public attributes (candle_power
and enabled
) and two methods (light
and extinguish
):
Subclassing LightSource
makes it possible to define a super candle which behaves the same way as LightSource
but provides a hundred times the amount of light:
The extends
syntax takes a list of class names to use as parents of the current class. The +
at the start of the candle_power
attribute name indicates that the current class extends the declaration of the attribute. In this case, the super candle overrides the default value of the light source, so any new SuperCandle
created has a light value of 100 candles. The other attribute and both methods are available on SuperCandle
instances; when you invoke light
or extinguish
on such an instance, Perl will look in LightSource::SuperCandle
for the method, then in the list of parents of the class. Ultimately it finds them in LightSource
.
Attribute inheritance works in a similar fashion, except that the act of constructing the instance makes all of the appropriate attributes available in the proper fashion (see perldoc Class::MOP
).
Method dispatch order is easy to understand in the case of single-parent inheritance. When a class has multiple parents (multiple inheritance), dispatch is less obvious. By default, Perl 5 provides a depth-first strategy of method resolution. It searches the class of the first named parent and all of its parents recursively before searching the classes of the subsequent named parents. This behavior is often confusing; avoid using multiple inheritance until you understand it and have exhausted all other alternatives. See perldoc mro
for more details about method resolution and dispatch strategies.
You may override methods in subclasses. Imagine a light that you cannot extinguish:
All calls to the extinguish
method for objects of this class will do nothing. Perl's method dispatch system will find this method and will not look for any methods of this name in any of the parent classes.
Sometimes an overridden method needs behavior from its parent as well. The override
command tells Moose that the subclass deliberately overrides the named method. The super()
function is available to dispatch from the overridding method to the overridden method:
This subclass adds a warning when trying to light or extinguish a light source that already has the current state. The super()
function dispatches to the nearest parent's implementation of the current method, per the normal Perl 5 method resolution order.
Inheriting from a parent class means that the child class and all of its instances will return true when you call the isa()
method on them:
To me this is one of the most exciting areas of development of Perl. sub-typing, along with type coercion rules (both in Moose::Util::TypeConstraints
), type libraries (MooseX::Types
) and read-only objects that you can still construct lazily allow you to efficiently layer assertions in a declarative fashion, into very easy to debug code. (Read-only objects will also later win when it comes to threading, STM, etc of course as people port their designs to Perl 6, but no need to scare readers with such crazy talk).
Moose provides many features you'd otherwise have to build for yourself with the default object orientation of Perl 5. While you can build everything you get with Moose yourself (see blessed_references), or cobble it together with a series of CPAN distributions, Moose is a coherent package which just works, includes good documentation, is part of many successful projects, and is under active development by an attentive and talented community.
By default, with Moose objects you do not have to worry about constructors and destructors, accessors, and encapsulation. Moose objects can extend and work with objects from the vanilla Perl 5 system. You also get metaprogramming--a way of accessing the implementation of the system through the system itself--and the concomitant extensibility. If you've ever wondered which methods are available on a class or an object or which attributes an object supports, this metaprogramming information is available with Moose:
You can even see which classes extend a given class:
See perldoc Class::MOP::Class
for more information about metaclass operations and perldoc Class::MOP
for Moose metaprogramming information.
Moose and its meta-object protocol (or MOP) offers the possibility of a better syntax for declaring and working with classes and objects in Perl 5. This is valid Perl 5 code:
The MooseX::Declare
extension from the CPAN uses a clever module called Devel::Declare
to add new syntax to Perl 5, specifically for Moose. The class
, role
, and method
keywords reduce the amount of boilerplate necessary to write good OO code in Perl 5. Note specifically the declarative nature of this example, as well as the now unnecessary my $self = shift;
line at the start of the age
method.
Certainly worth noting that the Devel::Declare
infrastructure is essentially core in Perl 5.12, though I am unaware of MooseX::
modules like MooseX::Method::Signatures
being ported to that yet.
One drawback of this approach is that you must be able to install CPAN modules (or a custom Perl 5 distribution such as Strawberry or Chocolate Perl which may include them for you), but in comparison to Perl 5's built-in object orientation, the advantage in cleanliness and simplicity of Moose should be obvious.
See perldoc Moose::Manual
for more information on using Moose.
Hey! The above document had some coding errors, which are explained below:
- Around line 3:
-
A non-empty Z<>
- Around line 49:
-
=end for without matching =begin. (Stack: [empty])
- Around line 457:
-
A non-empty Z<>
- Around line 593:
-
A non-empty Z<>
- Around line 811:
-
'=end' without a target?
- Around line 915:
-
'=end' without a target?