in reply to Re: interface.pm explained
in thread interface.pm explained

Thanks for the interesting comments!

I wasn't familiar with traits. If I correctly understand what they are, then:

  1. Traits are like interfaces in that they provide a way for a class to promise it fulfils some {interface, protocol} — that is, to say "I can do this".
  2. They are like a base class in that the trait itself can provide some implementation code of its own. (Perl traits also allow operator overloading definitions.)
  3. Unlike interfaces and base classes, but like mixins, all entities are pushed into the consuming module's namespace.
You suggest that traits are better than interfaces because they allow some code reuse; and are safer than multiple inheritance because (I presume) they avoid ordering problems. Since I'm new to this and haven't tried it out yet: can a class that consumes a trait override the code it receives from the trait? (Is this definable formally? Is it encouraged?) Apart from moving them to deterministic compile-time, how do traits help resolve ordering problems associated with MI? Do you simply get compiler errors when redefining subs/interfaces? What means does the programmer have to resolve these conflicts? (I saw something called "aliases", but if that's a proposed solution to the problem then I'm not sure how it works; I thought the strength of interfaces was that foreign code knows precisely which method to call and can assume that method fulfils a particular interface.)

Also, an implementation question. Suppose I have class Base that uses traits. Then along comes class Child. Where does it get its traits from? Presumably from the parent class, right? But then the traits aren't truly mixed in. The main problem with that is that it gets hard to specify (and implement) what should happen when Child consumes another trait of its own, that conflicts with something from Base.

I'm new to this, so please go gentle if there are obvious answers to these questions :)

(I planned the original post because I liked the clever implementation described, not because I wanted to advocate the use of interfaces; but if a language debate springs up all the better! There's a lot for me to learn here.)

Replies are listed 'Best First'.
Re^3: interface.pm explained
by Arunbear (Prior) on Aug 02, 2004 at 09:09 UTC
    Traits or Roles as they are called in Perl6 are described in detail in Apocalypse 12. On CPAN there are Class::Roles and Class::Role which aim to bring the Role functionality to Perl5.

      Traits (as described in the papers and implemented in Class::Trait) are not the same thing as Roles (as defined in the Apocalypse) anymore. It is my understanding that the original Traits paper was the inspiration for Roles, but they have really grown to differ in many ways, and really should not be thought of as the same thing anymore.

      -stvn
Re^3: interface.pm explained
by Ovid (Cardinal) on Aug 02, 2004 at 13:58 UTC

    Since I'm new to this and haven't tried it out yet: can a class that consumes a trait override the code it receives from the trait?

    Yes. I can simply choose not to import a particular method. Using Class::Trait syntax:

    use Class::Trait some_trait => { exclude => [ "methods_I_dont_want" ] };

    If you still need that code and don't want to exclude it, you can alias it to another name:

    use Class::Trait some_trait => { alias => { old_name => 'new_name' } };

    Apart from moving them to deterministic compile-time, how do traits help resolve ordering problems associated with MI?

    Let's say that you have a class which inherits from both a Bomb class and a GirlFriend class.

    package SomeThingOrOther; use base qw/GirlFriend Bomb/; ...

    (I realize you understand the ordering problem. I explain the following for the benefit of those who might be reading and not know.) Your class needs to explode() from Bomb, but unbeknownst to you, the GirlFriend class also has an explode() method! Now, because you used GirlFriend first in your use base ... statement, you get the wrong behavior and this can be very difficult to debug. Of course, if there's another duplicate method in your two base classes but you don't want the one in your Bomb class, you're life gets even more difficult. Now you need to use delegation or start hard coding classnames, neither of which is quite as easy as simply inheriting the data was supposed to be.

    Traits make this a trivial problem:

    use Class::Trait GirlFriend => { exclude => [ "explode" ] }; use Class::Trait Bomb => { exclude => [ "some_method_in_girlfriend" ] };

    Suppose I have class Base that uses traits. Then along comes class Child. Where does it get its traits from?

    The Child class should not know or care about where Base gets its methods. Are they traits or implemented directly? It should not matter. All Child needs to know is the published interface to Base. That's when the Child class can decide whether or not to override Base methods. Whether this is done through writing the methods directly or use of traits should not matter at this point.

    One final comment: some of my code snippets above can look daunting to those unfamiliar with traits. In reality, most traits are pretty straightforward.

    use Class::Trait 'TPrintable'; use Class::Trait 'TId';

    Hope this helps!

    Cheers,
    Ovid

    New address of my CGI Course.

      My interest was piqued, so I had to see what Java does when your class implements two interfaces with "conflicting" function signatures. (They're not strongly conflicting since there's no code that's competing like in MI.)

      Curiously, the following code compiles cleanly without even a warning — at least on Eclipse 3.0 with the default warning settings (and I couldn't find anything to change there that might be relevant).

      It's curous to me not because I don't see why this works. It's obvious that it *can* work. But interface clash means a design problem, and I'd expect this to at least warrant a warning.

      The Child class should not know or care about where Base gets its methods.

      Then there is problematic behavior in Class::Trait. Not sure if this is exactly a bug or a design contention, but the following is inconsistent with your point above. Quoting the docs:

        If a method label in a class conflicts with a method label in the trait (or composite trait), the class method is chosen and the trait method is discarded. This only applies to methods defined directly in the class's symbol table, methods inherited from a base class are overridden by the trait method.
      Since a trait inherited from Base is not flat (mixed in) in Child, overriding behavior is different.

      IMHO the default behavior for conflicts (of any sort) should be a fatal compile time error. If you don't do this you lose a significant advantage of protocols (using the more theoretic term here to distance from Java interfaces). If you think that this is wrong, at least make the behavior consistent. ("You" ne you personally, necessarily; I realize somebody else is maintaining the module now.)

        Actually, I think we see this from a different point of view as I don't think this behavior is inconsistent. Let's say I have an Animal class, a subclass of Organism. In this class I use an Eat trait (Insect might use this, too, for example), but since the author of Organism decided (for the sake of argument) that not all living things eat, there is no &eat method to inherit. A trait might be appropriate here because I don't want to redefine the method (which I would have to do with interfaces) and there's nothing else to logically inherit from. Further, delegating this to another class might not make a lot of sense.

        So now I decide that I want a Mammal class which inherits from Animal. If I want to override &Animal::eat, I need to explicitly code &Mammal::eat. Explicitly coding this should mean that I really, really want what I coded. Whether or not &Animal::eat came from a Trait or was hand-coded should be irrelevant to my desire to override it. I should not care about implementation details. Therefore, regardless of whether I hand-coded &Mammal::eat or got it from a Trait, this method should behave towards inherited methods exactly the same way. If I have a Trait method that overrides an inherited method, I as the programmer have the responsibility to know the published interfaces of the Classes and Traits that I use. On the other hand, an optional warning might be nice; I'll have to think about that.

        Does that make sense? I'm not sure I explained that well.

        Cheers,
        Ovid

        New address of my CGI Course.

Re^3: interface.pm explained
by stvn (Monsignor) on Aug 02, 2004 at 22:12 UTC

    Hello gaal, Ovid pointed me to this discussion, and since I am the author of Class::Trait, I thought I might pipe up and answer some of your questions.

    Traits are like interfaces in that they provide a way for a class to promise it fulfills some {interface, protocol} — that is, to say "I can do this".

    Actually, I think you need to invert your thinking on that. To start with, Traits aren't really classes, and to think of them as such may get you into trouble with them. And to think of Traits as being too much like interfaces and mix-ins, might also lead to problems. Traits are more akin to "deferred classes" as found in Eiffel. They are not meant to be complete, or to be able to stand on their own, they are building blocks for classes, but not really classes themselves. This node includes some documentation I originally wrote for Class::Trait, but ended up not including, it describes a formal langauge/calculus for traits (ripped off from on of the papers, I certainly didn't come up with that myself).

    But, anyway, back to the point. Traits themselves have requirements which must be fufilled by the class that chooses to use them. After the requirement is fufilled, and the trait is flattened into the class, nothing else happens. I did however, implement a is method into Class::Trait, which is added to the class using the traits. This is to be thought of being like isa is in perl, it will do a depth-first search of all the traits the class has used and return true of false depending upon whether the class used that trait.

    But here is where Traits differ from interfaces/protocols. After the class has used the trait,and it is incorporated, to all outside parties it is like nothing ever happened, and you implemented the methods yourself in the class directly. There is no implied contract between the class which used the traits and any other object in your system.

    They are like a base class in that the trait itself can provide some implementation code of its own.

    Again, best to not thing of Traits as classes. They are simply a set of methods collected into a grouping, which can be added into a class. Other than that though your statement is correct.

    Unlike interfaces and base classes, but like mix-ins, all entities are pushed into the consuming module's namespace.

    Yes, this is true. It is called flattening. Although I have to say, I didn't know that is what is done with mix-ins, although my experience with them is limited.

    Apart from moving them to deterministic compile-time, how do traits help resolve ordering problems associated with MI?

    There is no inheritance to be had, so no diamond problem can occur, this is the benefit of flattening the methods into the consuming class. Of course, I cannot guarantee that someone could not figure out a way in which MI problems might creep in, and therefore break Traits. But to the best of my knowledge, they avoid the issues of MI, by avoiding inheritance in general.

    Also, when multiple traits are added into a class, they are first combined into a composite trait. The rules for combining traits are described best in the paper "Traits - A Formal Model", which can be found on this page, a lesser description can be had in Re: Traits as Method Exporters, which I linked to above. The rules are grounded in set-theory and other mathematical esoteria of which I only know a limited amount about. The point is that you don't have the same rules when combining traits as you do when you combine classes with MI, and no more than one trait is ever combined into a class.

    Do you simply get compiler errors when redefining subs/interfaces?

    When you redefine them where? When combining Traits into a composite trait, method conflicts result in the exclusion of both methods, and that method's label is then added to the requirements list.

    The idea here is that you should manually resolve conflict up front with the exclude and alias options, and if one were to creep in unexpectedly, Traits make no claims to know what you meant to do, and so defer it back to you. The result is that when your class uses your trait, and does not fulfill the new requirement (created from the method conflict in the the composite trait) your compiler goes BOOM.

    As for the method conflict in a class and a trait, Ovid actually explains that. Remember Traits are not classes, and traits are subservient to classes.

    What means does the programmer have to resolve these conflicts? (I saw something called "aliases", but if that's a proposed solution to the problem then I'm not sure how it works;

    Aliasing allows you to rename a method, which can avoid a conflict since conflicts are checked based on method label (and if labels match, we also check to see if the code reference is the same too before deciding it is truly in conflict). Aliasing simply changes the label, nothing more, nothing less.

    I thought the strength of interfaces was that foreign code knows precisely which method to call and can assume that method fulfills a particular interface.
    Again, traits are not interfaces, and they have no contract outside of their relationship with the class that uses them.

    Also, an implementation question. Suppose I have class Base that uses traits. Then along comes class Child. Where does it get its traits from?

    It doesn't get them from anywhere (as Ovid says). The fact Base uses traits, is not known to the Child. Its is Base's concern only, as far as Child can tell, Base implemented its methods on its own.

    what should happen when Child consumes another trait of its own, that conflicts with something from Base.

    It won't conflict, at least not in the way I think you are thinking it will conflict. As far as Child knows, Base implemented its own methods. Any traits which Child uses are mostly unconcerned with Base. However, there are subtleties to this. If you are interested look at the test file "50_Trait_SUPER_test.t", which uses t/test_lib/Read.pm , t/test_lib/SyncRead.pm and t/test_lib/TSyncRead.pm. It is an implementation of an example given the papers about how Traits deal with and relate to the super class of the class that uses them.

    Hope this helps your understanding of traits :)

    -stvn