Object-oriented design has faltered somewhat on the overuse of inheritance and class hierarchies at the expense of polymorphism and true typed genericity. Traits and roles form one great approach to solving those problems in an efficient, resuable, and powerful way. They’re available in Perl 6, of course — but they’re also available in Perl 5.

Curtis Poe’s and Stevan Little’s Class::Trait is the most complete implementation of traits (or roles in Perl 6) available on the CPAN today. I recently explored it for an afternoon. Here’s what I found.

What are Traits

Traits come from two ideas. The first is that “traditional” OO systems, especially class-based, often have the severe limitation of encouraging inheritance and unwittingly discouraging other mechanisms of code reuse and genericity. The second is a research paper exploring this idea in the Smalltalk programming language.

A trait, also known as a role in Perl 6, is a discrete unit of behavior and state. It resembles a class in that sense, but you cannot instantiate a trait or role directly. Instead, you must compose it into a class. This provides code reuse unrelated to inheritance.

There’s more.

By defining traits, you can describe collections of behavior and state in terms of these traits. For example, your system may have traits such as Loggable, Stringable, and Persistable to describe behavior that many classes and objects must support. By specifying that the classes and objects you use support those traits–but not specifying how they do so–you can allow further decoupling and more flexibility in your design and use.

I realize that sounds very abstract, but think of it as a system more concerned with the capabilities of objects and classes than their parentage, where it’s more important that you can stringify something for printing, whether it’s a complex object, an exception, a string, or a number than that it is just a String, no more and no less.

Class::Trait uses Perl 5’s flexible-but-rarely-dogmatic-about-anything object system to support traits in a usable way.

How to Use Traits

To declare a trait, start to define a class as usual. You don’t need a constructor, as you likely expect people to compose the trait into existing classes which themselves have their own constructors. Do use Class::Trait.

I wrote a Serializable trait which describes the necessary behavior to serialize an object from memory to a string. It provides one method, serialize(), which you can call on an object to receive the string. It requires one method, get_attributes() from the composing class, so that it can get a data structure representing the object:

  package Serializable;

  use strict;
  use warnings;

  use Class::Trait 'base';

  our @REQUIRES  = 'get_attributes';

  sub serialize
  {
      die "Empty serializer in Serializable; use a concrete implementation.\n";
  }

  1;

Note the base parameter passed to Class::Trait. This registers the Serializable trait. As you can see, the only explicit other metadata is @REQUIRES, which contains a list of the names of all required methods. This interface isn’t great, and it can cause problems with advanced trait usage. There are hacks to get around it, but … ouch.

Using a trait is simple. I wrote two classes with slightly different object styles, HashBased and ArrayBased. Yes, I do like transparent examples. They both use a trait and provide the get_attributes() method. (Not providing one is a compile-time error.)

  #!/usr/bin/perl

  use strict;
  use warnings;

  package HashBased;

  use strict;
  use warnings;

  use Class::Trait 'Serializable';

  sub new
  {
      my ($class, %args) = @_;
      bless \%args, $class;
  }

  sub get_attributes
  {
      return { %{ +shift } };
  }

  1;

  package ArrayBased;

  use strict;
  use warnings;

  use Class::Trait 'Serializable';

  my %args_ids =
  (
      name => 0,
      age  => 1,
      id   => 2,
  );

  sub new
  {
      my $class = shift;

      my @args;

      while (@_ >= 2)
      {
          my ($name, $val) = splice( @_, 0, 2 );
          $args[ $args_ids{ $name } ] = $val;
      }
      bless \@args, $class;
  }

  sub get_attributes
  {
      my $self = shift;
      return { map { $_ => $self->[ $args_ids{ $_ } ] } keys %args_ids };
  }

  1;

When Perl compiles each class, it will load Serializable from disk (if not already), then try to compose its methods into the class. If there are failures, they will happen at compilation time. That’s a nice benefit.

(Collisions cause a subroutine redefinition warning. This could be stronger. In Perl 6, it’s not an error to compose a role into a class that provides some of what the role provides. It would be an error if the role overrode the class’s methods.)

It’s easy to write code to demonstrate that the traits work… at least except for the fatal serialize() method. Hold on; I’m going somewhere with it.

  my $hb = HashBased->new( name => 'Bob', age => 54, id => 200 );
  my $ab = ArrayBased->new( name => 'Jim', age => 58, id => 100 );

  print "HB: ", $hb->serialize(), "\n";
  print "AB: ", $ab->serialize(), "\n";

After creating one object of each class, it should be possible to call serialize() on them and get back the data in an appropriate format. It should be, except that serialize() from Serializable dies. Why? I intended it as an abstract base trait. Yes, I am abusing Class::Trait, but that’s what I do.

I wrote two other trait modules that inherit from Serializable. The first uses Data::Dumper to serialize the object’s data to a Perl data structure:

  package ToPerl;

  use strict;
  use warnings;

  use base 'Serializable';
  use Data::Dumper;

  our @REQUIRES = 'get_attributes';

  sub serialize
  {
      my $self = shift;
      my $atts = $self->get_attributes();
      return Dumper( $atts );
  }

  1;

The second uses YAML to produce a YAML string:

  package ToYAML;

  use strict;
  use warnings;

  use base 'Serializable';
  use YAML;

  our @REQUIRES = 'get_attributes';

  sub serialize
  {
      my $self = shift;
      my $atts = $self->get_attributes();
      return Dump( $atts );
  }

  1;

Note that there is a little bit of duplication in the @REQUIRES line. I don’t like that, but this was the cleanest solution.

Changing HashBased and ArrayBased to use these traits is easy. Just change one line from:

  use Class::Trait 'Serializable';

… to:

  use Class::Trait 'ToPerl';

Now the example code should run correctly.

(There are other ways to solve this problem too. Another option is to make Serializable a concrete role — that is, with no methods you have to override — and then make it parallelizable by passing in some sort of option for the serializer to use. That seems more difficult in this simple case, but it’s a great technique for other problems.)

Traits aren’t just for compile time. You can change the composed trait at run-time too. If Data::Dumper just isn’t working out for HashBased, write:

  Class::Trait->apply( HashBased => 'ToYAML' );

  print "HB (YAML): ", $hb->serialize(), "\n";

Now serializing these objects will produce YAML strings.

You can also apply traits to individual objects:

  Class::Trait->apply( $ab => 'ToYAML' );

  print "AB (YAML): ", $ab->serialize(), "\n";
  print "WB: ", $wb->serialize(), "\n";

Though the $ab object now serializes to YAML, the $wb object of the same class serializes to Perl, just as it did before.

This technique is immensely powerful, especially when you need to compose in data methods. I’ve used it successfully in a game to compose different View traits into my Model objects, for example.

How is the trait querying? It’s pretty good, but it has two slight problems. To see if a class or an object can perform a role, use the does() method:

  print $wb->does( 'ToPerl' );
  print HashBased->does( 'ToYAML' );

Of course, this ignores trait inheritance (and to be fair, I don’t know if the authors considered it; I didn’t bring it up until now). It also fails to catch the case where a class performs its own trait. I believe that every class implies the existence of a trait (or, more directly, roles are types and a class is a specialization of a role). This, sadly, fails:

  print HashBased->does( 'HashBased' );

Perl 5.10 has a new method in the UNIVERSAL class called DOES() that, well, does the same thing. It might be nice to standardize on the capitalization there. (I don’t really like it, but I do agree that it’s probably the right approach.)

Conclusion

These few drawbacks are easily solvable. Class::Trait does much, much more. Even so, using its simplest features has the potential to simplify your code immensely, while making it more generic and reusable and powerful. Roles are one of the best features of Perl 6’s object system, and Class::Trait does a fine job of making them available in Perl 5.

I highly recommend exploring this module and the concepts of traits and roles.