Beefy Boxes and Bandwidth Generously Provided by pair Networks
"be consistent"
 
PerlMonks  

Open to debate on mixins and traits.

by BrowserUk (Patriarch)
on Jun 02, 2004 at 16:41 UTC ( [id://359550]=perlmeditation: print w/replies, xml ) Need Help??

Since replying to Apocalypse 12 at Re: Re: Apocalypse 12, on and off, I've been reading and playing and digesting and trying to wrap my brain around traits. The recent thread Implementing a Mixin Class and particularly Re: Implementing a Mixin Class & Re: Re: Implementing a Mixin Class was the catalyst for this meditation.

It's a meditation because I think I disagree with the latter two assessments. I say I think, because the name, if not the concept is relatively new to me.

I am still allowing my skepticism of 'new programming concepts' to battle with my gut feeling and a little experimentation. The skepticism is born of many years of learning (willingly or forced) the latest, greatest paradigm, only to find out later that it is either

  • A new name for an old idea--that often as not didn't prove particularly useful the first time(s) around.
  • The, usually over-hyped, invention of some marketing dept. that is long on promise, short on real-world benefit and either expensive, impractical or just plain tedious to implement.

Or both.

My gut feeling is that this is a useful idea. I've yet to make enough realistic usage of them to consider myself convinced of their value, but whether you call them mixins, traits or interfaces*, the basic idea is fairly appealing.

(*) Struck in deference (and agreement) with chromatic's post below. Java interfaces are not the same, although they (brokenly) attempt to solve (part of) the same problem.

What

There are many types of functionality that are useful behaviours for many classes, regardless of how different the prime functionality of those classes may be. A couple of examples

  1. Stringification. When it comes to debugging, the ability to ask an object, regardless of it's class, to dump itself in a human readable, textual form is extremely useful.
  2. Persistance. Being able to request an object save/restore itself from some persistent storage medium, whether that is a RDBMS, a hierachal DB, a flat-file, CSV etc. is a common and useful function.
  3. Serialisation. Please send a copy of yourself to this destination. Email; SOAP; XML; network format binary; As distributed system become more common place, this a useful thing to do. With e-commerce and close-knit customer-supplier links becoming contractual obligations, this is becoming more and more important.
  4. Syncronisation. In multi-threading, multi-processing and clustered environments, this becomes very important.

Providing this type of behaviour for your own classes can be done in 4 ways.

  1. Roll your own.

    Each class invents or re-invents it's own methods for handling each of the above behaviours that it needs.

  2. Built-in to the base (universal) Object class.

    Every object in the system gets every behaviour whether it needs it or not.

  3. (Multi) Inheritance.

    The system provides classes for performing each of the behaviours and each class that needs them inherits from those as required.

  4. Mixins (aka. traits, aka. interfaces).

    Each of the behaviours is written as a 'dataless class' that cannot be instantiated. The methods to implement the behaviour are written so that they 'know' how to perform the required function for any class. Probably through introspection.

    The behaviour can be added to whole classes or individual instances at runtime. As many or few are required by the given application.

Why

Each of these has it's problems

  1. Roll-your own.

    These are the basic problems with all roll-your-own code.

    • It takes time.
    • It takes skill.
    • It duplicates effort.
    • It costs.
    • Maintainance is difficult.
    • The change of an underlying tenet of a behaviour--transport mechanism, DB vendor, CPU architecture, OS-platform-- requires hunt-the-code, global changes.
  2. Built-in to the base (universal) Object class.

    Sounds inviting, but the problem is overhead. If every object has to carry the methods, implemented or not, in it's V-table for each of these useful, but not universally applicable behaviours, then the costs, in terms of memory if nothing else, become prohibitive.

    The more built-in methods the base object class has, the more memory consumed by each class, and with some implementations, each instance. This can become a barrier to creating large numbers of small classes and/or instances.

  3. Multiple inheritance.

    The problems with multiple inheritance are well documented. Your either convinced that MI is a 'bad thing', or (you've never tried to write or maintain a large system that uses it :), you're not.

  4. Mixins (aka. traits, aka. interfaces).

    The main problem with these is that they are devilishly difficult to write well.

    At least in theory, they should

    • have no intrinsic state of their own.
    • be universally usable.

      They should 'know' how to do their thing, regardless of the implementation of the class(es) to which they are applied. This (I think) requires that the be able to introspect the their hosts and determine everything they need to know from them in order to carry out their behaviour.

    • avoid namespace clashes.

      One of the biggest practical benefits of OO is the avoidance of 'namespace-clashes'. Anyone who has written re-usable, procedural libraries will know the phenomena.

      Function names like DosSetNmPHandState() & MouGetNumQueEl() are just diabolical and GpiSetDefaultModelTransformMatrix() might be slightly clearer, but it's really no better.

      With the methods of mixins becoming a part of the host classes namespace, the problem of namespace clashes re-rears it's ugly head.

    • Comparability and equality.

      An application starts and retrieves a bunch of instances of some class, that it had saved during a previous run, from persistent storage.

      Their persistence was provided by attaching the :persistent trait to the instances that tested as isIncomplete() during global destruction when the application died, crashed or was otherwise terminated.

      At some point in the run of the application, a new instance of the class is created as a result of an in-bound datastream from another machine (network, customer, continent). As a result, this instance has the :serializable trait.

      The application now needs to know if this instance is the same as one of the existing incomplete instances.

      There is such an instance that is identical except for the difference in their traits.

      Is it the same?

It's not hard to invent scenario's that fit either possible answer.

Putting the above downsides of mixins aside, the benefits I think I see are:

  • Re-use.

    Written once. Lives in one place. Easier to maintain or change system wide etc.

  • Reduced complexity.

    Especially compared to MI.

  • Reduced overhead.

    Relative to built-in approach. Only those classes/instances that need the trait, carry the overhead of having it.

  • Reduction in inheritance tree depth.

    No need to add a new layer to the inheritance tree for every new feature.

    io.(Buffered|Filtered|ByteArray|WordArray)(File|Pipe)(Input|Output)Str +eam

    You get

    my $io :buffered :filtered :utf = IO::Any->open( ... );

    The difference?

    • No need to instantiate 3, 4 or 5 levels of object passing each to the constructor of the next to get what you need.
    • No need to cast.
    • When a new, useful trait becomes available, there is no need to re-write/deprecate the entire library to make use of it.

      my $io :buffered :filtered :utf :compressed :encrypted = IO::Any->open +( ... );

      Note: :compressed not :zipped. The decision as to which compression algorithm can be encapsulated by installing/loading a different implementation of the :compressed trait. Application or system wide.

      Ditto for the :encrypted.

Compare that with the Java runtime.

Covered a little above, but a little more on the possible uses of traits.

  1. :persistent - This could be an RDBMS or Storable or XML or YAML or CSV or....
  2. :synchronized - between processors or clusters or customers.
  3. :encrypted - XOR, PKE, SSH, ...
  4. :compressed - TAR, zip, gzip, arc, ...
  5. :buffered - stdio, proxy, memoized, cached, disk, tape ...
  6. :num - int, float, pic(99999), BigInt, Network, BigEndian ...
  7. :encoded - ANSI, Extended-ANSI, UTF-8, UTF-16, UTF-32, Chinese, Klingon ...
  8. :Your imagination?

Examine what is said, not who speaks.
"Efficiency is intelligent laziness." -David Dunham
"Think for yourself!" - Abigail

Replies are listed 'Best First'.
Re: Open to debate on mixins and traits.
by chromatic (Archbishop) on Jun 02, 2004 at 16:50 UTC

    I'd suggest that interfaces are different from mixins, in that they don't provide any code. (Yeah, they could, but when I use the word "interface", people familiar with Java flip a switch in their heads and they think that Java's terrible, terrible, almost completely useless implementation of the idea is what I want. It's not.)

    The whole point of roles in Perl 6 is two-fold.

    First, as you point out, there's a lot of behavior shared vertically between classes that doesn't make sense to fit into a hierarchy. It'd sure be nice to share that code without having to copy and paste it or do complicated things to your inheritance tree.

    Second, it's nice to have meaningful names attached to that behavior that you can use to identify the capabilities of objects. This makes generic programming much, much easier (which I contend is the real point of OO). If you want to print something, you care that it can stringify, not that it is or inherits from String somewhere.

      I hate to poke the beehive, but what's so terrible about Java interfaces?

      Chris
      M-x auto-bs-mode

        I like to describe Java interfaces as "multiple inheritance from abstract base classes that live in a secondary type system." Here are four drawbacks:

        • They're a secondary type system completely separate from the primary type system in declaration and use. Is your object a foo or does it merely implement a foo? Why should there be a distinction?
        • They don't provide any code (unless you want to talk about constants defined in interfaces, for which Java's rules have always confused me).
        • You can't add them after the fact to a class without editing its source code and recompiling. This probably springs from the first problem. (If they'd solved the first problem, you wouldn't need to do this!)
        • The standard library doesn't take them seriously enough. If you're going to declare String as final, at least give me some way of providing something that works like a string that the standard library can accept. I think this is also part of the first problem above.

        The first problem is the most damning from the purist's perspective. Java's designers recognized a real problem (a single-rooted inheritance scheme is inadequate to express real solutions), but they created a secondary type system and didn't take it seriously.

        From a practical perspective, the second problem is worse. The designers recognized that a single-rooted inheritance scheme is inadequate to express complex behaviors of real problems, but they completely failed to allow for any code reuse! "Objects that implement this interface all behave similarly, but multiple inheritance is bad so you'll have to copy and paste code to make it work."

      I totally agree.


      Examine what is said, not who speaks.
      "Efficiency is intelligent laziness." -David Dunham
      "Think for yourself!" - Abigail
Re: Open to debate on mixins and traits.
by dragonchild (Archbishop) on Jun 02, 2004 at 17:52 UTC
    <disclaimer>I have never used interfaces, mixins, roles, etc and am basing this reply completely on A12</disclaimer>

    Anything done poorly is ... well ... poor. How many of us have seen OO-done-poorly? pass-by-reference as a substitution for globals? The examples are endless.

    Now, I don't anticipate the average programmer using any roles they wrote themselves. I do anticipate a standard set of roles released to CPAN, probably mapping somewhat closely to your list. I anticipate roles being something that the expert Perl developer will approach very cautiously and treat as a plaything which could be very handy. For a while.

    Within 2-3 years, I anticipate roles being part of the standard lexicon. This is going to be very much like how lexical closures were dealt with in the Perl4/Perl5 transition. Or, in smaller vein, how our and use warnings; were handled in the 5.00x -> 5.6.x transition. They're new, most people don't know them intimately, and we'll learn how to use them.

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

    Then there are Damian modules.... *sigh* ... that's not about being less-lazy -- that's about being on some really good drugs -- you know, there is no spoon. - flyingmoose

    I shouldn't have to say this, but any code, unless otherwise stated, is untested

Re: Open to debate on mixins and traits.
by stvn (Monsignor) on Jun 02, 2004 at 19:08 UTC

    I find your terminology a bit confusing. There are Perl 6 "traits", then there are Perl 6 "Roles", which are somewhat based on the ideas in the Traits papers.

    I don't really see Perl 6 traits as being all that much like Mix-ins though. Personally, I am not so sure what Perl 6 traits will be most useful for. The Apocolypse says:

    A class declaration may apply various traits to the class. (A trait is a property applied at compile time.) When you apply a trait, you're accepting whatever it is that that trait does to your class, which could be pretty much anything. Traits do things to classes. Do not confuse traits with roles, which are sworn to play a subservient role to the class. Traits can do whatever they jolly well please to your class's metadata.
    It sounds to me like a place where only the genius or the insane will venture (at least regularly). Either way, I am still not 100% sure what good they will provide other then to implement "deep-level" things like AOP, or to replace the "symbol-table-hacking" stuff. But I am not even sure about that, of course if anyone cares to enlighten me, it would be much appreciated.

    As for Perl 6 Roles, these I feel are more akin to Mix-ins, although not quite really. From my (limited) understanding, Perl 6 Roles will really be a fancy way to set up a delegation-type relationship between classes and Roles (which I see as being partial or deffered classes). This is very different from the Traits idea it was originally based upon (which in some ways are very much like Mix-ins, just with a clear set of rules and a means to enforce them).

    Confusion over terminology aside, I would like to comment on some of what you list as problems for "mixins, traits, etc":

    be universally usable
    I don't believe this to be true, and IMO one of the reasons why things like this get a bad wrap. Any experienced OO programmer knows that you have to know when to say when, or you end up with the God-Object-Uber-Base-Class-From-Hell. It is highly unlikely that you can create mix-ins or traits that can be all things to all classes. At some point you need to weigh the benefits of code-reuse vs. code-complexity. When it really comes down to it, polymorphism a more powerful means of code-reuse anyway.

    I would recommend reading this Traits paper to see how they refactored the Smalltalk Collection hierarchy with Traits. It shows that building Traits, should not be treated as just building small, incomplete classes to be squished together at a later time, but instead a rethinking of how classes are/can be composed. Its very informative, and likely relevant to how one might use Perl 6 traits/Roles as well.

    Keep in mind, that there is nothing in the Traits rulebook that says you cannot implement a Trait with no methods and only requirements (which amounts to the equivalent of a Java interface). And I would expect that Perl 6 Roles and traits works that way too.

    avoid namespace clashes
    Well, I imagine Perl 6 traits, being compile-time beasts, will not even allow this. Perl 6 Roles have means of working around this by fully qualifying the name. And Traits are much like Perl 6 traits in this, in that there are rules to govern namespace conflicts and they will all either be resolved at compile time, or just not compile.
    Comparability and equality
    ~snip~
    There is such an instance that is identical except for the difference in their traits.
    Is it the same?
    I would argue that they are clearly not the same instance, but that it not to say that they are not equal to one another. Maybe I am misunderstanding you, but I see determining comparability and equality as not really having to do with the concept of "mixins and traits" and more having to do with the requirements of your specific application. For some classes equality may be their UUID, for others, it is equivilant internal data, its really all about what you want to do with it. Again, maybe I am misunderstanding you here, please correct me if I am.

    In the end, all these things (Perl 6 traits, Perl 6 Roles, Traits (ala the papers)) are still toys. And much as dragonchild points out, they will be feared initially, and if they prove useful, will eventually become accepted parts of how we all code OO. Even after having written Class::Trait, I have yet to find a real good use for it, other than something to play around with. And I am waiting on either Class::Roles or Class::Role to see how Perl 6 Roles might be useful, and I suppose I will need to wait for Perl 6 itself to see how Perl 6 traits might play out.

    -stvn

      Yes. I did/do tend to mix up the terminology. When I used the word "trait", I was thinking of the Traits definition and Perl6 Roles.

      But then I threw in the :trait syntax which is wrong! But that just serves to emphasis the (my?) confusion both with the naming and the P6 differentiation, which still leave me perplexed.

      I'm still thinking about your other points. I may get back to you if I have questions arising :) Thanks.


      Examine what is said, not who speaks.
      "Efficiency is intelligent laziness." -David Dunham
      "Think for yourself!" - Abigail
        But that just serves to emphasis the (my?) confusion both with the naming and the P6 differentiation, which still leave me perplexed.

        It's perfectly simple.

        We have Perl 6 traits, which are different from Self traits, both of which are different from Xerox Star traits, all three of which are different from the Traits described in traits paper - which Perl 6 will call roles. Except roles have state with the traits paper Traits don't.

        Traits (that is Perl 6 traits, not the trait paper traits, Self traits or Xerox Star traits) are roles applied to declared items like classes and containers. Unless they are applied at runtime in which case they are called properties.

        Confused? You won't be after the next episode of... Soap!

        ;-) * 0.5

        (Okay, I admit it, I've still not found the time to more than skim A12 so I may have got some of the above wrong, and there is probably a nice simple structure in there that's not managed to find its way into my head yet)

Re: Open to debate on mixins and traits.
by adrianh (Chancellor) on Jun 02, 2004 at 18:21 UTC
    Providing this type of behaviour for your own classes can be done in 4 ways.

    Shall we say that there are at least four ways :-)

    Aspect Oriented Programming is an obvious fifth direction that some people are taking. Creating a new language that incorporated the cross-cutting concern (in AOP speak) as part of the core language would be a sixth. Sticking the common behaviour in a meta-class in those languages that support them would be a seventh.

    Any more for any more?

      <generic cop-out>I was kind of limiting my thoughts to Perl:) </generic cop-out>

      Aspect Oriented Programming is an obvious fifth direction that some people are taking.

      That said, from my limited (and hastily reviewed), understanding of AOP, it isn't an means of implementation (be done), it's a ... um ... philosophy.

      It doesn't even have to involve objects (their claim). It basically says that the logical architecture doesn't have to be either a single-rooted tree nor a multi-root tree, it can be a graph. Which, as far as my math goes means that you can stick things in anywhere and cross-connect them however you like.

      There is (are?) concrete implementation(s) of AOP (Aspectj and ?). I don't know how they are implemented, but I suspect that it basically comes down to essentially the same as mixins/traits in that extra methods get attached directly or indirectly to the vtable.

      The difference is the logical view rather than the physical implementation. The basic goals are the same as mixins: The separation and re-use of common functionality without imposing super-dependant structure on the logical view of the system.

      Creating a new language that incorporated the cross-cutting concern (in AOP speak) as part of the core language would be a sixth.

      Isn't that exactly what the :trait notation of P6 is doing?

      Sticking the common behaviour in a meta-class in those languages that support them would be a seventh.

      I think that I would view this as the same as my second option; "Built-in to the base (universal) Object class", if the meta-class is the base meta-class.

      If your not proposing sticking the common behaviour in the base meta-class, but into meta-classes for each of your real classes, then all you've done is move the goal posts. You would still end up with cut&paste re-use, or one of the four options.


      Examine what is said, not who speaks.
      "Efficiency is intelligent laziness." -David Dunham
      "Think for yourself!" - Abigail

        I was kind of limiting my thoughts to Perl:)
        What about Aspect?

        There is (are?) concrete implementation(s) of AOP (Aspectj and ?). I don't know how they are implemented, but I suspect that it basically comes down to essentially the same as mixins/traits in that extra methods get attached directly or indirectly to the vtable.

        My (also limited) understanding of AOP is that it is not really just for object composition (as mix-ins/traits are), and therefore not directly related to OOP (although you rarely see AOP without OOP).

        As far as AOP implementation, it actually looks to me like Aspect is full of symbol-table madness, while AspectJ has an Aspect weaver which is essentially a compiler pre-processor.

        Creating a new language that incorporated the cross-cutting concern (in AOP speak) as part of the core language would be a sixth.
        Isn't that exactly what the :trait notation of P6 is doing?
        Similar, but not exactly. But again, my understanding of both is limited, but I don't see them as being exactly the same.

        -stvn
Re: Open to debate on mixins and traits.
by perrin (Chancellor) on Jun 02, 2004 at 18:51 UTC
    The namespace collision issue is enough to keep me away from mixins. It's too much like the old perl4-style library files (or PHP).
Re: Open to debate on mixins and traits.
by bsb (Priest) on Jun 03, 2004 at 07:46 UTC
    I recommend reading the traits paper (where ever it is). It gives specific solutions to all the problems you mentioned (with the exception of comparability).

    The solutions are along the lines of: "automatically do the right thing if unambiguous, otherwise the using class must resolve the problem". Eg. namespace clashes must be manually resolved (aliasing at import or using fully qualified names).

    The article is also clearly disentangles some problems with traditional OO concepts and shows some non-obvious benefits of their approach. Eg simpler method search, class flattening. The presence of MI in perl means we don't automatically get all the benefits but can opt in. TIMTOWTDI

    Brad

      I don't actually see the namespace issue being a major problem, it was just something that came up whilst I was reading/thinking about mixins/traits/roles.

      I think I envisage that there would be a dozen (or two at most) major mixin APIs along the lines of those I mentioned in the OP.

      For the most part, these APIs would override the names of built-ins. Eg. The is Compressed and is Encrypted Roles would override the readline() and writeline() built-ins such that reading from a file would cause the data to be decompressed and/or deencrypted with printing to a file reversing the process.

      If both are used on the same file, then the overrides would nest in whichever order the Roles are initially loaded? Configuration parameters to the Role would be passed as arguments to the use line? After which there would be little need for further direct interaction between the host class and the Role?

      In effect, most Roles would be similar to ties in that they would present predefined APIs with "well-known names" to their callers. Hence my inviting comparison with Java interfaces.

      There would probably be many implementations of each Role, (zip, gzip arc etc.), but each would comply to the same API thereby allowing the substitution of one implementation for another without the need for the hosting module to change.

      It's not clear to me how a particular implementation of a Role would be selected for a given application nor how you would select a specific implementation (perhaps also through the use time configuration). Harder to envisage is how one might use two or more implementations of the same Role simultaneously, but that will probaly become clearer once we get a bit more information (E12?).

      It will be quite interesting to observe the debate on what 'standard# Roles shoudl exist and what there APIs should be. It will become quite fundemental that these are 'done right first time' as once they becomes established, they will effectively becomes standard extensions to the language with all the hysterysis against change and evolution that that implies.

      It's fun to think about how they might evolve, but I guess I am being somewhat premature in my thinking and will need to wait for LW, TheDamian et al to publish more information before trying to draw any real conclusions.


      Examine what is said, not who speaks.
      "Efficiency is intelligent laziness." -David Dunham
      "Think for yourself!" - Abigail
        I think you and I are on different trips here. I don't know what the real story is though.

        I don't see why Perl needs interfaces in any form, they're just there to satisfy the compiler in statically typed languages, aren't they?

        I think of roles as reuseable partial classes. So they add generic behaviour, customized by the state accessors they use (or metadata). I'm more going from the paper than A12, though.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlmeditation [id://359550]
Approved by kvale
Front-paged by guha
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others studying the Monastery: (6)
As of 2024-03-28 14:32 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found