Skip to content

Latest commit

 

History

History
923 lines (634 loc) · 27.8 KB

moose.pod

File metadata and controls

923 lines (634 loc) · 27.8 KB

Moose

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.

Classes

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.

Methods

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:

Attributes

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.

Encapsulation

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.

Polymorphism

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 Cats (the attributes) and what the program knows that Cats 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.

Roles

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.

Inheritance

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):

Inheritance and Attributes

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.

Inheritance and Methods

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.

Inheritance and isa()

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:

Sub-types, Type coercion, lazy yet immutable data

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 and Perl 5 OO

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.

POD ERRORS

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?