Traits in Perl

Ovid on 2004-02-12T20:49:10

chromatic has Class::Roles on the CPAN. This is an attempt to implement traits in Perl. I think it's a great start,but there are a few features of traits that it doesn't implement yet. I've been thinking about the problem and I think I've got it licked. When all of the traits (roles) are used, a dynamically created interface is generated and it validates the integrity of the flattened class. It's a bit more complicated than it sounds, but I'll send chromatic some code when I'm done -- if it works.


The Wait

chromatic on 2004-02-15T01:15:49

I'm waiting to see what shakes out of A12 before I do much more work on it. Very few people really understand the point of roles; I hope that Larry will explain it far better than I've been able to do.

Re:The Wait

Ovid on 2004-02-15T02:36:41

I don't blame you. It looks like there's a lot of potential there, but how it integrates into Perl 6 will be interesting.

I just finished an alpha version of Class::Trait and I'm writing the docs now. I don't have a tarball uploaded, but the interface looks like this (borrowing heavily from your examples):

package Person;
# make them a lifeguard, but ensure that they use &Trait::Dog::swim and not
# &Trait::LifeGuard::swim
use Class::Trait 'Trait::LifeGuard';
use Class::Trait 'Trait::Dog' => { explicit => 'swim' };

# the dog will want a &doggie_treat, but since it's AUTOLOADed or inherited,
# we must explicitly promise that it will be available
Class::Trait->promise('doggie_treat');
Class::Trait->assemble;

1;

package Trait::LifeGuard;

sub swim {
  return Trait::LifeGuard::_swim();
}

sub save_drowning_swimmer {
  my (undef, $swimmer) = @_;
  # save the swimmer
}

1;

package Trait::Dog;

Class::Trait->expects('doggie_treat');

sub swim {
  my ($class, $target) = @_;
  Trait::Dog::_eat($class->doggie_treat);
  # swim to $target
}

sub _eat {
  ...
}

1;

Essentially, it boils down to traits having expectations and the primary class making promises. If the primary class does not have a given method in its symbol table (due to AUTOLOAD or inheritance), then it promises that the method will be available when called (Class::Trait->promise('doggie_treat');). A trait is expected to not make promises at the current time (that may change).

A trait might need methods it doesn't provide, though, so it sets expectations

package Trait::Dog;
Class::Trait->expects('doggie_treat');

As the primary class and traits are used, a tally of all promises, expectations, and actual methods is made. When the Class::Trait->assemble method is called, the interface is validated and flattened into the primary class.

To resolve conflicts, you must explicitly request a given method from a trait.

use Class::Trait 'Trait::Dog' => { explicit => 'swim' };

All methods in a package that do not begin with an underscore (or the "import" method) are added to the list of traits to be flattened. If a trait wants a private helper method, it must call it via the fully qualified name (or a private subref).

So far, the tests pass, but it does have a few limitations. I worked hard to make the interface as self-explanatory as possible. If you or anyone else is interested, I can provide a download link for the tarball when it's ready.

Re:The Wait

Ovid on 2004-02-15T02:42:58

I forgot to mention that since I don't know how Perl 6 roles are to be implemented, my design goals were driven by my understanding of the original traits document. That's one of the reasons why I call them "traits" instead of "roles". I've thought about uploading this to the CPAN, but I have two concerns. First, I don't want to cause potential confusion when Perl 6 comes out. Second, since there are already beginning implementations of roles, I don't necessarily want to provide competing modules unless I am very confident in what mine can do.

Also, I've discovered that when I write weird stuff like this, I get very little feedback. Very few people understand what traits do (I'm not entirely sure that I've got it right), so I suspect that few, if any, would use my module. Further, I base my idea on the idea of composite runtime interfaces. That's a bizarre concept, so I don't know if it's a good idea.