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
Providing this type of behaviour for your own classes can be done in 4 ways.
Each class invents or re-invents it's own methods for handling each of the above behaviours that it needs.
Every object in the system gets every behaviour whether it needs it or not.
The system provides classes for performing each of the behaviours and each class that needs them inherits from those as required.
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
These are the basic problems with all roll-your-own code.
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.
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.
The main problem with these is that they are devilishly difficult to write well.
At least in theory, they should
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.
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.
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:
Written once. Lives in one place. Easier to maintain or change system wide etc.
Especially compared to MI.
Relative to built-in approach. Only those classes/instances that need the trait, carry the overhead of having it.
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( ... );
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.
In reply to Open to debate on mixins and traits. by BrowserUk
For: | Use: | ||
& | & | ||
< | < | ||
> | > | ||
[ | [ | ||
] | ] |