[Pdx-pm] Poll: traits

Ovid publiustemp-pdxpm at yahoo.com
Sat Nov 19 11:17:11 PST 2005

--- Eric Wilhelm <scratchcomputing at gmail.com> wrote:

> http://www.iam.unibe.ch/~scg/cgi-bin/scgbib.cgi/abstract=yes?Scha03a
> What I love about perl's oo is that we really don't need all of the 
> formalisms that appear in other languages.

Yes and no.  There's no question that Perl's model is extremely
flexible, but sometimes you want to break instead of bend.  The
problems with multiple inheritance are extremely well-known and
well-docuemanted.   Ruby-style mixins and Java interfaces are two
attempts to avoid this (note that both languages only support single
inheritance) but both of these are also broken.  Mixins have ordering
problems and interfaces force you to redefine the implementation every
time -- even if the implementation doesn't change.

> "A trait is essentially a group of pure methods that serves as a 
> building block for classes and is a primitive unit of code reuse."
> So, can you spell that in Perl?  It sounds suspiciously like 
> it's nothing more than a module that allows you to import
> some methods into your namespace.  Or is that a mixin?

That's the basics of it (traits are actually really simple to use. 
It's the theory behind traits which throws a few people off).

Traits, however, are far more powerful than mixins.  Imagine, for
example, that you have two traits:  TSpouse and TBomb.  Each of those
traits provides two methods, fuse() and explode().  You want to
institute a "FamilyMember" class and use those traits.  Naturally, you
want to use the TSpouse::explode() method because it's non-lethal
(usually).  You want to use the TBomb::fuse() method because you can
control the timing.

If these were mixins, you have a problem.  With Ruby, you would get the
fuse() and explode() methods from whichever of these mixins you used
*last*.  If you want one from each, you have to use delegation or
something similar.

With traits, if there are method conflicts, the traits fail *at compile
time* and you have to explicitly resolve the problem:

  package FamilyMember;
  use Class::Trait
    TSpouse => { exclude => ['fuse'] },
    TBomb   => { exclude => ['explode'] };

And now FamilyMember->can('explode') (from TSpouse) and
FamilyMember->can('fuse') (from TBomb).

However, if the TSpouse and TBomb traits had no conflicts, it would be
as simple as this:

  use Class::Trait qw(TSpouse TBomb);

And you've have everything you need.

In a nutshell, the reasoning behind traits is simple.  A class needs to
provide *everything* you should be able to do with that class.  Thus,
it needs to be complete.  However, if think of classes for code reuse
benefits (for example, when we inherit from classes), then they should
be as small as possible so we don't accidentally pull in more stuff
than we want or need.

So classes should generally be both complete and small, but these needs
often conflict.  Since traits are both small and provide code reuse,
they satisfy both requirements.  Further, unlike Java interfaces, they
provide the implementation so you don't have to keep rewriting it. 
Like Java interfaces, they fail *at compile time* if you've not used
them correctly.  Unlike Ruby mixins, they don't have ordering problems.

I'm still working on Class::Trait and doing some deep internals work
with it, but the basic features are there.


If this message is a response to a question on a mailing list, please send
follow up questions to the list.

Web Programming with Perl -- http://users.easystreet.com/ovid/cgi_course/

More information about the Pdx-pm-list mailing list