Monday, August 11, 2008

Scala Traits

One of my favorite Scala features is traits. To a Java programmer, a trait is like an interface with implementation. The potential ambiguity of multiple inheritance is solved by defining a linearization order for all traits and superclasses. Given that ordering, you can figure out which classes or traits override which others. Diamond inheritance is not a problem.

Traits for Shared Code

For my Java/Swing program JRaceman, I defined a set of GUI classes to simplify my programmatic construction of screens. Each class took as constructor parameters a ResourceSource that provided access to resources (for localization), a resource key prefix, and an action to take when that GUI element was selected (such as a button push or menu item selection). For example, my MenuAction class looked something like this:
//Java code
public class MenuAction extends JMenuItem implements ActionListener {
    public MenuAction(ResourceSource resourceSource, String resourcePrefix) {
        //Look up resources for label, toolTip and set those values
        addActionListener(this);
    }
    public void actionPerformed(ActionEvent ev) {
        action();
    }
    public void action() { /* do nothing */ }
}
I have left out all the details from the above listing. The total file size for this class is about 140 lines.

The application would instantiate a menu item something like this:
//Java code
    MenuAction b = new MenuAction(resourceSource,"menu.Foo.Open") {
        public void action() { fooOpen(); }
    };
I have similar Java classes for ButtonAction, CheckBoxAction, and CheckBoxMenuAction. Each of these has a lot of similar code to set labels and toolTips, as well as other things such as handling exceptions while executing the action. Because each of my classes extended a different Swing class, I was unable to effectively share this code, so there is a bunch of duplicated code in these classes.

For my Mimprint photo printing application, which I converted from Java to Scala, my equivalent class to the MenuAction Java class looks something like this:
class SMenuItem(rSource:SResources, rPrefix:String)(action: =>Unit)
        extends JMenuItem(rSource.getResourceString(rPrefix+".label"))
        with SComponent with SCompToolPrompt {
    setupToolTip(rSource, rPrefix)
    setupToolPrompt(rSource, rPrefix)
    setupIcon(rSource, rPrefix)
    setupActionListener(rSource, action)
}
That's it. That's the entire class. Less than ten lines of code. The actual file, with comments, is 18 lines long. Almost the entire contents of that 140-line MenuAction.java file has been moved into the two traits SComponent and SCompToolPrompt, where the code can be shared with my Scala implementations of Button, CheckBox, etc. The SMenuItem, SComponent and SCompToolPrompt classes in Scala are together about the same size as the MenuAction class in Java, so by itself this is not a win for code size, but now I can use SComponent and SCompToolPrompt for my other Swing wrapper classes, so each of those classes reduces down from around 100 to less than 20 lines of code with no corresponding increase in any other files.

Even without the improvement in lines of code, I like the ability to break up one class into multiple files for organizational purposes.

The application invokes SMenuItem something like this:
    val mi = new SMenuItem(rSource, "menu.Foo.Open")(fooOpen)
With the shared code for my GUI components now housed in one place, such that I no longer had to maintain multiple copies of the code, I was also motivated to add more features to that file, such as better handling of an exception during the action. It also made it much easier to more uniformly apply the features supported by SComponent. For example, my Java GUI class did not support icons; but SComponent.scala does include icon support, so I can add icon support to my Scala GUI classes just by adding the one line call to setupIcon.

Traits as Facade

I have also used traits as a nice way to implement the Facade pattern. In the above examples, the SResources type is actually a trait (shown here without the boilerplate and comments that appear in the real source file):
trait SResources {
    def getResourceString(key:String) : String
    def getResourceStringOption(key: String) : Option[String]
    def getResourceFormatted(key: String, arg: Any) : String
    def getResourceFormatted(key: String, arg: Array[Any]) : String
}
I then implemented a SResourcesFacade trait to allow a class to act as an SResources by delegating all calls to a member object that in turn implements the SResources trait. In this way, I can have one real source for resources, then easily turn other classes into proxies for that resource source.
trait SResourcesFacade extends SResources {
    //Extending class must define this value
    protected val sResourcesBase : SResources

    def getResourceString(key:String) =
            sResourcesBase.getResourceString(key)
    def getResourceStringOption(key: String) =
            sResourcesBase.getResourceStringOption(key)
    def getResourceFormatted(key: String, arg: Any) =
            sResourcesBase.getResourceFormatted(key, arg)
    def getResourceFormatted(key: String, args: Array[Any]) =
            sResourcesBase.getResourceFormatted(key, args)
}
Now to make a class into an SResources class, I only need to make the class extend SResourcesFacade and define a value for sResourcesBase, like this:
class Foo extends SResourcesFacade {
    val sResourcesBase : SResources = new RealResources()
}
where RealResources is a class that implements the SResources trait.

This particular facade only has four methods, but this same technique would work for a facade with any number of methods.

You can find the complete Scala code for the above examples (which is not exactly the same as the code given here) in the packages net.jimmc.swing (for the GUI examples) and net.jimmc.util (for the facade examples) in the sources to Mimprint, which is distributed under the GPL.

5 comments:

Chris Bouzek said...

The code snippets run together in my Google reader. Are they in "pre" blocks or something else?

Jim McBeath said...

Chris: The code snippets are formatted using a div with associated CSS that includes "pre" formatting. Unfortunately Google Reader throws away the CSS and the class attribute on the divs. I have added real <pre> tags around the code snippets in all of my posts. Hit the Refresh button on the blog view to pick up the changes.

Chris Bouzek said...

Ah, that worked. Thanks!

Toland said...

Hi Jim,

Got another technical faux pax. "My Misperception of Scala's Ordered Trait" breaks your whole site in Safari and presumably Chrome because of an unescaped "<%" in the third snippet. Technically markup in PRE blocks must still be escaped. Some blogging plugins automatically handle this but not Blogger.

Great writing :)

Jim McBeath said...

toland: Thanks for pointing that out. Fixed.