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

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.


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.


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