Over in my use.perl journal, I discussed a module I've been writing named Class::Trait. This module attempts to implement traits (pdf) in Perl. chromatic has Class::Roles on the CPAN and this is an attempt to implement Perl 6 roles in Perl 5, but he's decided to hold off further development until Apocalypse 12 comes out as that is the document that will fully explain the intended implementation of roles. Roles, incidentally, are essentially the same thing as "traits" in the paper above, but the word "trait" (compile time properties) is already used in Perl 6.

I've considered uploading my module to the CPAN, but I've held off for a couple of reasons. First, I'm concerned that this might cause confusion with Perl 6 traits/roles (though this should be less of a concern if I document it well). The other reason is pretty straightforward: people don't know much about traits and if no one is likely to use the module, I don't see much sense in adding to the terminology glut. If you think that traits are something you would be interested in using, please let me know.

My design considerations:

Since Perl 6 roles are not completely defined, I ignored them and used the original traits paper for guidance.

Here's my first pass (this code works, but I don't have a tarball uploaded anywhere), borrowing heavily from chromatic's Class::Roles example (because my examples were so politically incorrect that even I was a bit put off by them).

We have a person who wishes to have a LifeGuard trait, but only wants to dog paddle. Thus, even though a lifeguard can swim, we want the dog's &swim method. However, a dog might not swim without a doggie treat and the person doesn't have a &doggie_treat method, but it will AUTOLOAD one on demand.

package Person; use Class::Trait 'LifeGuard'; use Class::Trait 'Dog' => { explicit => 'swim' }; Class::Trait->promise('doggie_treat'); Class::Trait->assemble; # this flattens the traits into this class sub AUTOLOAD { # we'll make &doggie_treat, if needed } 1;

The traits look like this:

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 { # $class might actually be an instance, but we should assume # nothing about the internals of the object my ($class, $target) = @_; Trait::Dog::_eat($class->doggie_treat); # swim to $target } sub _eat { # this must be called via a fully qualified name because private # methods are not flattened into the primary class } 1;

The way this works is simple (in theory, not in code). As traits are added, promises made and expectations listed, I build a composite interface (similar to a Java interface). When Class::Trait->assemble (should I call that 'flatten'?) is called, the interface is validated and conflicts are resolved. Unresolved conflicts are fatal, as are unmet expectations.

Questions:

Cheers,
Ovid

New address of my CGI Course.

Replies are listed 'Best First'.
Re: Class::Trait to the CPAN?
by Zaxo (Archbishop) on Feb 16, 2004 at 21:17 UTC

    This is the definition given in the C++ Standard:

    17.1.18       traits class          [defns.traits]
    a class that encapsulates a set of types and functions necessary for template classes and template functions to manipulate objects of types for which they are instantiated. Traits classes defined in clauses 21, 22 and 27 are chararacter traits, which provide the character handling support needed by the string and iostream classes.

    -- ISO/IEC 14882:1998(E)

    In Standard C++ these are used to package the basic character operations for equality, dictionary ordering, string length, searching, movement and copying. There, they are characterized by the requirements that they present a unified interface and that each character class provides a concrete implementation of that interface. They sound very like the set of conditions you cite.

    Under Perl's looser type regimen, generic (template) programming is usually regarded as automatic. An instance class's methods are assumed to provide the needed interface, and it is up to the programmer to make sure they automatically do ;-)

    It seems to me that there are two fairly natural implementations available in Perl 5.

    1. By inheritance from a virtual base class. The interface functions are written there in terms of declared but undefined primitive methods which are private to the derived class, which must implement them somehow. Flattening is achieved by ordinary inheritance. The usual caveats of multiple inheritance apply.
    2. By Perl's attribute mechanism. That is notationally convenient, but is still a black art for many Perlers (myself included). Perhaps this is less an alternative than an element of #1.

    One less-known aspect of C++ traits is that their methods do not throw. Is some no-error convention appropriate for Perl traits?

    After Compline,
    Zaxo

Re: Class::Trait to the CPAN?
by hardburn (Abbot) on Feb 16, 2004 at 17:15 UTC

    The other reason is pretty straightforward: people don't know much about traits and if no one is likely to use the module, I don't see much sense in adding to the terminology glut.

    IMHO, people aren't going to learn much about traits unless they can use them in practical examples (at least, I sure won't, though I'm more of a "learn by doing" type of person). I'd like to see a useable version of traits so I can figure out how to best use the concept before Perl6 gets going.

    ----
    : () { :|:& };:

    Note: All code is untested, unless otherwise stated

Re: Class::Trait to the CPAN?
by dragonchild (Archbishop) on Feb 16, 2004 at 21:21 UTC
    Personally, I have an actual production need for this. After I read the paper, I idly thought of implementing Class::Trait myself, (but am waaay too lazy).

    I've got a Base class, called Object::Base. I've also got Object::Singleton and Object::ReadOnly. So, now, I want to be able to create a readonly singleton. Ok. Not a problem. Except, I don't want to create a Object::ReadOnlySingleton class. (I anticipate adding 3-4 other orthogonal attributes.)

    The problem is depth-first method searching. There's some dovetailing between how O::Singleton and O::ReadOnly do their work. O::Singleton calls a method that O::ReadOnly overrides, and vice-versa. If I inherit from them both, I miss whichever is second in @ISA.

    So, in short - yes, I want this. I'll be treating Traits as a neat implementation of Decorator, but it still works. :-)

    ------
    We are the carpenters and bricklayers of the Information Age.

    Please remember that I'm crufty and crochety. All opinions are purely mine and all code is untested, unless otherwise specified.

      I'll be treating Traits as a neat implementation of Decorator

      That's what I did with Class::Roles (or whichever one I wrote) and Mail::Action. It's quite nice.

Re: Class::Trait to the CPAN?
by simonm (Vicar) on Feb 16, 2004 at 21:40 UTC
    I'd encourage you to upload it to CPAN. The module collection is large and diverse enough that additional dilution or "glut" seems like a misplaced concern.

    Particularly in interesting areas like this where there's active ongoing discussion and consideration, I think it's helpful to have a handful of different implementations available that people can put through their real-world paces... And looking at different takes on a similar problem can help to clarify some of the core commonalities and separate out various useful idioms.

    In response to the questions in the OP, no, I haven't yet used a traits-based system, but I have spent a bunch of time hacking different types of method aggregation, generation, and composition techniques in Perl, so yes, I would be interested in seeing how or if traits would help me address some of the same concerns.

    It would be very helpful to have a more realistic example that both synopsized the Class::Trait interface and also demonstrated how such traits can be useful... I haven't yet been able to visualize a compelling example case, but I'll give it some more thought.

Re: Class::Trait to the CPAN?
by adrianh (Chancellor) on Feb 16, 2004 at 23:42 UTC
Re: Class::Trait to the CPAN?
by halley (Prior) on Feb 17, 2004 at 14:41 UTC
    I guess I independently invented this idea a few years back, but instead of the word 'trait' I used the word 'quality.' I use the term 'attach' for adding a quality to a given object instance, because I also support removing (via 'detach') qualities at runtime also. My qualities do support state information, which does constrain the object model to support that.

    (I'll be digging into the linked PDF and other folks' ideas here to see how I can improve my own model. Thanks!)

    My implementations in Java supported runtime attachment and detachment of qualities, but my draft Perl implementation has been a little heavy and inelegant so far.

    These are very handy in design of various roleplaying games; you can add the "edible" quality/trait to an object and it now supports the verb 'eat.'

    If classes are nouns, qualities/traits are adjectives.

    Once you get used to the idea of qualities/traits, your class hierarchy is a lot different: most classes are really just groups of qualities without much to do directly. For example, a typical player-character in a MUD, just after drinking a magical potion, might look like:

    class Human is Mammal with Bipedal, Sentient, Inventoried, Skilled, Clothed instance 3453 is Human with Character, Flying, Enchanted

    After the flying-potion enchantment wears off, just remove Enchanted, which in turn can remove Flying.

    --
    [ e d @ h a l l e y . c c ]

Re: Class::Trait to the CPAN?
by Abigail-II (Bishop) on Feb 16, 2004 at 17:03 UTC
    Have you ever used traits in any language?
    Except for some exercises about interfaces I did during a Java course, the answer is no.
    Would you use a traits implementation in Perl?
    Unlikely.
    How would you improve the interface above?
    No constructive answer. But looking at your code, I'm at a loss as what is happening. I'm trying to map it on Java's interface model, but I don't succeed. This maybe though because I remember Java's interface model wrongly.

    Abigail

      My code and explanation don't really stand alone. If the original traits paper is read, it makes more sense. If you're familiar with mixins, then traits can be loosely thought of as mixins that have conflicts explicitly resolved (thus eliminating the ordering problem of mixins). Also, unlike mixins (as I understand them), a trait may require certain methods (expectations) that it does not itself implement. Thus, if you have a polygon class and you want furry polygons (whatever that means), then you might use a "Furry" trait. That trait might expect a paint method, but not implement it directly. Instead, it expects that it will be provided once the traits are flattened into the primary class.

      I see now that my description of interfaces was misleading. In Java, of course, an interface is merely a listing of methods that a class must implement. This is checked at compile time. For Class::Trait, I internally add methods to an "interface" and this interface is then validated when the Class::Trait->assemble method is called. Thus, if the "Furry" trait expects a paint method, this is added to the interface. The "interface" that I describe is dynamically generated and not validated until all required traits are examined. When assemble() is called, the methods are flattened and if a paint method is not found, the program dies, thus ensuring that, at the very least, the required methods are present (though there's no way in any language I know of to verify that those methods do what they should, though being able to specify method signatures would help).

      Cheers,
      Ovid

      New address of my CGI Course.

        So, it's basically a
        CHECK { die unless __PACKAGE__ -> can ("paint"); }
        except that the actually check is delayed till run time?

        In that case, I'd rather see an interface like:

        use Class:Trait qw /paint/;
        with no explicite assemble call, but have the checking done at compile time. That might be useful if you write a module that are only useful if callbacks are installed.

        Abigail

Re: Class::Trait to the CPAN?
by stvn (Monsignor) on Feb 17, 2004 at 21:00 UTC
    Have you ever used traits in any language?
    Nope.
    Would you use a traits implementation in Perl?
    Depends upon who good it was :)
    How would you improve the interface above?

    To start with I would not use the Trait::* namespace in the way you are. It is making assumptions that my application does not have another need for that namespace. I see what you are trying to do with it, and I recomend that you make all trait's inherit from a base Trait class instead.

    I would also combine the 2 use statements into one, so that:

    use Class::Trait 'LifeGuard'; use Class::Trait 'Dog' => { explicit => 'swim' };
    would turn into :
    use Class::Trait ( 'Trait::LifeGuard', 'Trait::Dog' => { explicit => 'swim' } );
    Your Class::Trait::import() code would just need to look-ahead after every trait name it found to see if there is a hash-ref to process. Pretty simple.

    If all your trait classes inherit from a Trait base, further compile time checking can be done too. You can basically check that each trait being imported is a valid Trait object and is actually implemented (i.e. - exists in the symbol table).

    I would also get rid of all this:

    Class::Trait->promise('doggie_treat'); Class::Trait->assemble; # this flattens the traits into this class sub AUTOLOAD {  # we'll make &doggie_treat, if needed }
    The promise should be auto-checked at compile-time when you load Trait::Dog with the use statement. Basically you get the list of promises for each Trait and then make sure they exist in (at a minimum) the symbol table class importing said Trait. This would allow you to use AUTOLOAD as you did above by just adding sub doggie_treat; somewhere, but I would discourage the use of AUTOLOAD because IMO it weakens the promise.

    As traits are added, promises made and expectations listed, I build a composite interface (similar to a Java interface). When Class::Trait->assemble (should I call that 'flatten'?) is called, the interface is validated and conflicts are resolved.

    I am not sure what you are actually doing by this description, but IMO the act of "assembling" the code (or "flattening" as the paper refers to it), should be done also at compile time. So that by the time you would reach the CHECK stage of perl compilation cycle all your traits methods should be attached and promises checked (not just made) and in general you should have a trait-object which would incur little if any additional run-time cost.

    This means that I think you should alias all your trait methods to be members of the class that is using them (this is how they describe the implementation in paper). Just a simple construct like this would do:

    *{"${using_pkg}::$trait_method_name"} = \&{"${trait_pkg}::$trait_metho +d_name"}
    Ideally the fact your class uses traits, would never be known by any other class or module that it might encounter. This may be an idealistic POV, but its something to strive for :).

    Taking all these things into account your examples would look like this:

    package Person; use Class::Trait ( 'Trait::LifeGuard', 'Trait::Dog' => { explicit => 'swim' } ); sub doggie_treat { my ($self) = @_; return "Snausages,.. yummmm"; } 1;
    The new (slightly expanded) traits look like this:
    package Trait::LifeGuard; # make it inherit from some # kind of base Trait object use base 'Class::Trait::TraitBase'; sub swim { my ($self, $target) = @_; # would probably want to pass on any # arguments here return Trait::LifeGuard::_swim($self, $target); } # i am fleshing this out a bit to show # how if $self->swim was actually # the Trait::Dog implementation it # would still work sub save_drowning_swimmer { my ($self, $swimmer) = @_; my $shore = $self->station()->location(); $self->swim($swimmer->location()); $self->grab($swimmer); $self->swim($shore); } # i used these two methods, so lets # define them here anyway sub grab {} sub station {} 1; package Trait::Dog; # make it inherit from some # kind of base Trait object use base 'Class::Trait::TraitBase'; # make this into a variable of some # kind, kind of like @EXPORT maybe? my @EXPECTS = ('doggie_treat'); sub swim { my ($self, $target) = @_; # since we know that the 'swim' method is now # a valid method in our $self object, we can call # 'doggie_treat' on $self with confidence since all # this will have been checked at compile time Trait::Dog::_eat($self->doggie_treat()); # swim to $target # passing on any arguments here # that might be nessecary ... Trait::Dog::_swim($self, $target); } sub _eat { # this must be called via a fully qualified name because private # methods are not flattened into the primary class } 1;
    Probably your best bet as to an example/test would be to try and built the 'Shape' example found in the traits paper itself. They explain it is great detail and you can use that as a bar to hold your implementation against. You may want to pick up a Smalltalk book if you really want to understand what they are doing there too.

    -stvn
Re: Class::Trait to the CPAN?
by Anonymous Monk on Feb 17, 2004 at 07:59 UTC
    Ovid, you asked:
    * Have you ever used traits in any language?

    Have You? On anything other than toy examples? It sure doesn't sound like it (apologies if that is incorrect). Might it not be a good idea to try out Traits yourself on at least one real world project before releasing it where unsuspecting people might assume it was designed and written by someone with actual working knowledge.

      That's actually an excellent point. ++. I read the traits paper and saw a lot of potential, so I decided to implement the idea. However, given that I've not found much real-world usage, there's not much prior art for me to go on, other than following the Smalltalk examples (and I don't know Smalltalk well). Hence this post seeking advice.

      The package is currently at version 0.02, so it's pretty clear that it's alpha code, but I should add a strong caveats section to the docs -- namely that I've not used traits before in any real project and thus am less sure of the interface or even the best usage.

      You wrote that it doesn't sound like I've used traits before. Did you get that impression because of some misunderstanding on my part? If so, I'd love to have it cleared up.

      Cheers,
      Ovid

      New address of my CGI Course.

        You wrote that it doesn't sound like I've used traits before. Did you get that impression because of some misunderstanding on my part? If so, I'd love to have it cleared up.

        I got that impression simply from the lack of any discussion of real world usage in your posts here and on use.perl. And from the kinds of questions you were asking.

        Don't get me wrong. I do not think it is a bad idea that you are writing this module, I just think that CPAN'ing it is somewhat premature given your lack of working knowledge of Traits.

        I'd suggest at least trying it out on one of your existing (real) projects to get a solid feel for how Traits can affect your design (composability wise). First, using what you know of Traits from the theoretical perspective, write yourself some 'Use Cases' for how you would like to apply Traits (that's right, programmer's should write Use Cases for themselves when designing new libraries, modules, methodologies, etc. After all, the programmer is the "user/customer" of such technologies). Then look to reworking an existing project with a non-trivial class/object hierarchy that might benefit from composing with Traits. Let your design be informed by theory, but driven by practice.

Re: Class::Trait to the CPAN?
by stvn (Monsignor) on Feb 18, 2004 at 20:12 UTC

    I was searching around on the web for more info on Traits (I may be needing to implement them or something like them in C#/.NET/MSILasm in the near future). And I actually found this page which has the original paper, along with a technical "experience report" on implementing the Smalltalk Collection class heirarchy with Traits as well as a paper on a formal model for Traits. I am printing them out right now for the train ride home, but haven't read them yet. Ovid, I thought you would especially be interested in this, and maybe chromatic too (maybe even Larry, Damien & the Perl 6 posse as well (assuming they already don't know about them)).

    Update:
    Just also found this paper (not for the faint of heart) - A typed calculus of traits (PDF). Which came from this page on Lamda The Ultimate.

    -stvn

      Lambda The Ultimate is a great Web site for making me feel very, very stupid :)

      Cheers,
      Ovid

      New address of my CGI Course.