NAME

Moose::Cookbook::Recipe10 - The Moose::Role example

SYNOPSIS

  package Eq;
  use Moose::Role;
  
  requires 'equal_to';
  
  sub not_equal_to { 
      my ($self, $other) = @_;
      not $self->equal_to($other);
  }
  
  package Comparable;
  use Moose::Role;
  
  with 'Eq';
  
  requires 'compare';
  
  sub equal_to {
      my ($self, $other) = @_;
      $self->compare($other) == 0;
  }    
  
  sub greater_than {
      my ($self, $other) = @_;
      $self->compare($other) == 1;
  }    
  
  sub less_than {
      my ($self, $other) = @_;
      $self->compare($other) == -1;
  }
  
  sub greater_than_or_equal_to {
      my ($self, $other) = @_;
      $self->greater_than($other) || $self->equal_to($other);
  }        
  
  sub less_than_or_equal_to {
      my ($self, $other) = @_;
      $self->less_than($other) || $self->equal_to($other);
  }  
  
  package Printable;
  use Moose::Role;
  
  requires 'to_string';    
  
  package US::Currency;
  use Moose;
  
  with 'Comparable', 'Printable';
  
  has 'amount' => (is => 'rw', isa => 'Num', default => 0);
  
  sub compare {
      my ($self, $other) = @_;
      $self->amount <=> $other->amount;
  }
  
  sub to_string {
      my $self = shift;
      sprintf '$%0.2f USD' => $self->amount
  }

DESCRIPTION

In this recipe we examine the role support provided in Moose. "Roles" may be described in many ways, but there are two main ways in which they are used: as interfaces, and as a means of code reuse. This recipe demonstrates the construction and incorporation of roles that define comparison and display of objects.

Let's start by examining Eq. You'll notice that instead of the familiar use Moose you might be expecting, here we use Moose::Role to make it clear that this is a role. We encounter a new keyword, requires:

  requires 'equal_to';

What this does is to indicate that any class which "consumes" (that is to say, "includes using with", as we'll see a little later) the Eq role must include an equal_to method, whether this is provided by the class itself, one of its superclasses, or another role consumed by the class (1).

In addition to requiring an equal_to method, Eq defines a not_equal_to method, which simply inverts the result of equal_to. Defining additional methods in this way, by using only a few base methods that target classes must define, is a useful pattern to provide maximum functionality with minimum effort.

After the minimal Eq, we next move on to Comparable. The first thing you will notice is another new keyword, with:

  with 'Eq';

with is used to provide a list of roles which this class (or role) consumes. Here, Comparable only consumes one role (Eq). In effect, it is as if we defined a not_equal_to method within Comparable, and also promised to fulfill the requirement of an equal_to method.

Comparable itself states that it requires compare. Again, it means that any classes consuming this role must implement a compare method.

  requires 'compare';

Comparable defines an equal_to method which satisfies the Eq role's requirements. This, along with a number of other methods (greater_than, less_than, greater_than_or_equal_to, and less_than_or_equal_to) is simply defined in terms of compare, once again demonstrating the pattern of defining a number of utility methods in terms of only a single method that the target class need implement.

  sub equal_to {
      my ($self, $other) = @_;
      $self->compare($other) == 0;
  }
  
  sub greater_than {
      my ($self, $other) = @_;
      $self->compare($other) == 1;
  }    
  
  sub less_than {
      my ($self, $other) = @_;
      $self->compare($other) == -1;
  }
  
  sub greater_than_or_equal_to {
      my ($self, $other) = @_;
      $self->greater_than($other) || $self->equal_to($other);
  }        
  
  sub less_than_or_equal_to {
      my ($self, $other) = @_;
      $self->less_than($other) || $self->equal_to($other);
  }

Next up is Printable. This is a very simple role, akin to Eq. It merely requires a to_string method.

Finally, we come to US::Currency, a class that allows us to reap the benefits of our hard work. This is a regular Moose class, so we include the normal use Moose. It consumes both Comparable and Printable, as the following line shows:

  with 'Comparable', 'Printable';

It also defines a regular Moose attribute, amount, with a type constraint of Num and a default of 0:

  has 'amount' => (is => 'rw', isa => 'Num', default => 0);

Now we come to the core of the class. First up, we define a compare method:

  sub compare {
      my ($self, $other) = @_;
      $self->amount <=> $other->amount;
  }

As you can see, it simply compares the amount attribute of this object with the amount attribute of the other object passed to it. With the single definition of this method, we gain the following methods for free: equal_to, greater_than, less_than, greater_than_or_equal_to and less_than_or_equal_to.

We end the class with a definition of the to_string method, which formats the amount attribute for display:

  sub to_string {
      my $self = shift;
      sprintf '$%0.2f USD' => $self->amount
  }

CONCLUSION

This recipe has shown that roles can be very powerful and immensely useful, and save a great deal of repetition.

FOOTNOTES

(1)

At present, method requirements from roles cannot be satisfied by attribute accessors. This is a limitation of Moose, and will most likely be rectified in a future release.

AUTHOR

Stevan Little <stevan@iinteractive.com>

COPYRIGHT AND LICENSE

Copyright 2006-2008 by Infinity Interactive, Inc.

http://www.iinteractive.com

This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.