Stories
Slash Boxes
Comments
NOTE: use Perl; is on undef hiatus. You can read content, but you can't post it. More info will be forthcoming forthcomingly.

All the Perl that's Practical to Extract and Report

use Perl Log In

Log In

[ Create a new account ]

Ovid (2709)

Ovid
  (email not shown publicly)
http://publius-ovidius.livejournal.com/
AOL IM: ovidperl (Add Buddy, Send Message)

Stuff with the Perl Foundation. A couple of patches in the Perl core. A few CPAN modules. That about sums it up.

Journal of Ovid (2709)

Friday September 16, 2005
07:34 PM

Allomorphism and Traits

[ #26748 ]

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.

The Fine Print: The following comments are owned by whoever posted them. We are not responsible for them in any way.
 Full
 Abbreviated
 Hidden
More | Login | Reply
Loading... please wait.
  • 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.

    • Is that any different from the idea of "interface" (as in Java), or is it just a different name for the same thing?
      • 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.

        • Could you explain how Java interfaces are broken and how Roles save the day?
          • 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

    • Sounds like Mac::Glue ... $bbedit->open vs $finder->open, etc.?
  • I'm not sure what traits solve (in this case) that mixins would not have solved, unless leaving the class hierarchy unaltered was critical to you. Anyway, I gotta admit, Class::Trait is pretty cool.
    • 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.

      • 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 [unibe.ch] ) 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);