You can read about allomorphism here. Don't. You'll hurt yourself.
I read and reread that article until I hurt myself. I couldn't get it. However, chromatic and others talk about the raw power of allomophism so it must be important, right?
After doing a fair amount of reading and thinking about it, I think I can safely describe allomorphism as follows (please correct me if I'm wrong): allomorphism in programming is the property whereby objects not related by inheritance still present the same set or subset of behaviors. In Ruby, this is "duck typing". If I call $object->save, I really don't care what class that object is. I just want to know that I've somehow persisted the object (I probably shouldn't care how, either) and that I can retrieve it again later. That's allomorphism.
In traits we can say that a particular class does something and it magically can do it. Traits are powerful but not well understood. In fact, while I've wanted to have a chance to use them, I've deliberately not done so as I haven't wanted to use them unless I really saw a clear need for it. Now I have.
I have three unrelated classes. We'll call them XSLT, REST (Representational State Transfer) and REST::Dispatch. The XSLT class is the repository of all things XSLT for the system I'm working on. The REST class knows how to communicate with a Web server and the Dispatch class. The latter class knows how to talk to the REST class and how to talk to our data store.
While the REST and Dispatch classes are somewhat tightly coupled, it's via a "has-a" relationship. Unfortunately, they both have to know how to talk to the XSLT class. The Dispatch class has to know how ask the XSLT class for the location of the XSLT files so it can insert this as the stylesheet link in the XML it outputs. That is the only way it talks to XSLT. However, the client may not know how to process the XSLT so it can request that the XML be transformed on the server. Thus, the REST class needs to to ask the XSLT class to do the transformation. That's the only way the rest class talks to XSLT.
XSLT / \ / D R i Server <--> E <-----> s <--> Data store S p T a t c h
So far, so good. We have three classes that need to communicate, but the communication is fairly lightweight. How does allomorphism come into play? And traits?
This system is getting fairly large. We use Test::Class to create a set of test classes that mirror the actual classes. Now, however, I have a problem. Unit testing is fine, but I also need integration testing. Because the user agent can request that the server handle the XSLT transformation, I not only need to know that the XSLT class can produce the correct XHTML, I also need to know that the REST class can do the same thing via its request to the XSLT class. I also need to know that the Dispatch class serves the correct XML and that the REST class can send that along unchanged.
In short, I have three test classes which need to test very similar things but none of them are related via inheritance. The solution? Traits. I've now created traits that provide the shared behavior of these classes. Steven Little has produced a new version of Class::Trait which eliminates some of the difficulties I've encountered and I hope to incorporate this soon. (Currently, I've hacked around the problem with export trickery.)
The win here is that all of my shared behavior is in one spot. I don't need to dispatch to special handler classes. I don't need to duplicate code. I don't need to worry that I might have an inheritance nightmare. I just use the traits. Each of the three classes must provide a method that specifies which data store classes and fields we'll be working with at any given time and a proper trait can verify at compile time that this method is available. Thus, I've leveraged allomorphism to make my code simpler and if anything I need is missing, it blows up at compile time, not run time. This has the added benefit of making sure that I haven't corrupted my data store by failing at runtime. I've been forced to commit data at one point because I have to run a web server in my tests. Yuck.
It seems a bit strange at first and some will object that others will not understand, but that should never stand in the way of using a superior technical solution. Are traits truly that solution? We'll see.
I like to think of allomorphism as describing objects or classes that have the same semantics for a set of behaviors but may be unrelated structurally.
Re:Alternate Explanation
itub on 2005-09-19T13:49:06
Is that any different from the idea of "interface" (as in Java), or is it just a different name for the same thing?Re:Alternate Explanation
chromatic on 2005-09-19T17:26:25
Interfaces in Java are horribly broken, but if you ignore the abominable implementation entirely and reconstruct the problem the Java designers almost tried to solve, you can get close to the idea.
Re:Alternate Explanation
vjo on 2005-09-20T07:52:51
Could you explain how Java interfaces are broken and how Roles save the day?Re:Alternate Explanation
chromatic on 2005-09-20T08:28:27
Java interfaces and types have separate namespaces. Thus if you have a closed library from which you cannot inherit or which you cannot modify, you cannot mark a new type as allomorphic (polymorphically equivalent). It's not really generic programming if you have to modify existing code to make it more generic.
Java interfaces also don't provide any default implementation. Whereas roles don't dictate any particular mechanism by which the types achieve their allomorphism, Java interfaces are effectively multiple inheritance from abstract base classes (except for the namespace problem). This makes code reuse much more difficult and promotes otherwise unnecessarily deep inheritance hierarchies. It also encourages the arbitrary distinction between extension and implementation.
Basically the Java designers saw a real problem -- the real world doesn't fit neatly into a singly-rooted inheritance hierarchy -- but didn't go far enough to allow genericity based on semantic behavior without having to copy and paste code or, worse, modify existing code.
Roles (and I show my bias here) fix all of those problems and make even cooler things possible -- parametrized roles, for example, allow even further specialization and customization. Think of them as class factories that can generate code that conforms to the role based on parameters you provide.
I certainly look forward to writing less code.
Re:Alternate Explanation
pudge on 2005-09-22T00:12:43
Sounds like Mac::Glue... $bbedit->open vs $finder->open, etc.?
Re:Mixins
Ovid on 2005-09-20T16:33:44
Well, since you specifically say "in this case", I suspect you already know the differences between traits and mixins. I just never considered using mixins since they're fundamentally broken and don't provide everything that traits do. Even if they weren't broken, I would probably still go with traits for the extra features. Starting with mixins only to switch to traits when I need the goodies means maintenance work down the road.
Re:Mixins
bshanks on 2010-01-21T03:36:15
i'm unclear on the difference between mixins and traits, could someone explain it to me?
i skimmed this paper (Traits: Composable Units of Behavior http://scg.unibe.ch/archive/papers/Scha03aTraits.pdf ) and my conclusion was that the difference is that traits differ from mixins in two ways: (1) traits don't allow one to add new state (fields) to a class, (2) in the case of a name collision when you add multiple traits to a class, none of the colliding methods are imported (you must explicitly alias them); whereas with mixins, the mixin to be imported last overrides previously imported methods. are there other differences as well?
thanks