Contents


Java.next

Mixins and traits

Mix new behaviors into Groovy and Scala classes

Comments

Content series:

This content is part # of # in the series: Java.next

Stay tuned for additional content in this series.

This content is part of the series:Java.next

Stay tuned for additional content in this series.

The developers of the Java language were well-versed in C++ and other languages that include multiple inheritance, whereby classes can inherit from an arbitrary number of parents. One of the problems with multiple inheritance is that it's impossible to determine which parent inherited functionality is derived from. This problem is called the diamond problem (see Related topics). The diamond problem and other complexities that are inherent in multiple inheritance inspired the Java language designers to opt for single inheritance plus interfaces.

Interfaces define semantics but not behavior. They work well for defining method signatures and data abstractions, and all of the Java.next languages support Java interfaces with no essential changes. However, some cross-cutting concerns don't fit into a single-inheritance-plus-interfaces model. This misalignment led to the necessity of external mechanisms for the Java language such as aspect-oriented programming. Two of the Java.next languages — Groovy and Scala — handle such concerns at another level of extension by using a language construct called a mixin or trait. This article explains Groovy mixins and Scala traits and shows how to use them. (Clojure handles much of the same functionality through protocols, which I cover in Java.next: Extension without inheritance, Part 2.)

Mixins

Some early object-oriented languages defined a class's attributes and methods together in a single block of code, whereupon the class definition was complete. In other languages, developers can define the attributes in one place but defer the method definitions until later and "mix" them into the class at the appropriate time. As object-oriented languages evolved, so did the details of how mixins work with modern languages.

In Ruby, Groovy, and similar languages, mixins augment existing class hierarchies as a cross between an interface and parent class. Like interfaces, mixins both act as types for instanceof checks and follow the same extension rule. You can apply an unlimited number of mixins to a class. Unlike interfaces, mixins not only specify the method signatures but also can implement the signatures' behavior.

In the first languages that included mixins, mixins contained only methods but no state such as member variables. Now many languages (Groovy among them) include stateful mixins. Scala traits also act statefully.

Groovy mixins

Groovy implements mixins through either the metaClass.mixin() method or the @Mixin annotation. (The @Mixin annotation in turn uses Groovy Abstract Syntax Tree (AST) transformations for the requisite metaprogramming plumbing.) The example in Listing 1 uses metaClass.mixin() to give the File class the ability to create compressed ZIP files:

Listing 1. Mixing a zip() method into the File class
class Zipper {

  def zip(dest) {
      new ZipOutputStream(new FileOutputStream(dest))
          .withStream { ZipOutputStream zos ->
            eachFileRecurse { f ->
              if (!f.isDirectory()) {
                zos.putNextEntry(new ZipEntry(f.getPath()))
                new FileInputStream(f).withStream { s ->
                    zos << s
                    zos.closeEntry()
                }
              }
            }
          }
  }

  static {
    File.metaClass.mixin(Zipper)
  }

}

In Listing 1, I create a Zipper class that contains both the new zip() method and the wiring to add that method to the existing File class. The zip() method's (unremarkable) Groovy code recursively creates a ZIP file. The last part of the listing wires the new method into the existing File class by using a static initializer. As in the Java language, a static class initializer runs as the class loads. The static initializer is the ideal location for augmentation code because the initializer is guaranteed to run before any code that relies on the enhancement. In Listing 1, the mixin() method adds the zip() method to File.

In "Extension without inheritance, Part 1," I covered two Groovy mechanisms — ExpandoMetaClass and category classes — that you can use to add, change, or remove methods on existing classes. Use of mixin() to add methods has the same end result as adding methods with either ExpandoMetaClass or category classes, but the implementation differs. Consider the mixin example in Listing 2:

Listing 2. Mixins manipulating the inheritance hierarchy
import groovy.transform.ToString

class DebugInfo {
  def getWhoAmI() {
    println "${this.class} <- ${super.class.name} 
    <<-- ${this.getClass().getSuperclass().name}"
  }
}

@ToString class Person {
  def name, age
}

@ToString class Employee extends Person {
  def id, role
}

@ToString class Manager extends Employee {
  def suiteNo
}


Person.mixin(DebugInfo)

def p = new Person(name: "Pete", age: 33)
def e = new Employee(name: "Fred", age: 25, id:"FRE", role:"Manager")
def m = new Manager(name: "Burns", id: "001", suiteNo: "1A")

p.whoAmI
e.whoAmI
m.whoAmI

In Listing 2, I create a class called DebugInfo, which contains a single getWhoAmI property definition. Within this property, I print out some details of the class, such as the current class and both the super and getClass().getSuperClass() properties' perspective on parentage. Next, I create a simple class hierarchy that consists of Person, Employee, and Manager.

I then mix the DebugInfo class into the Person class, which resides at the top of the hierarchy. Because the whoAmI property exists for Person, it exists for its child classes too.

In the output, you can see (and might be surprised to see) that the DebugInfo class insinuates itself into the inheritance hierarchy:

class Person <- DebugInfo <<-- java.lang.Object
class Employee <- DebugInfo <<-- Person
class Manager <- DebugInfo <<-- Employee

Mixin methods must fit within Groovy's already complex relationships for method resolution. The different return values for Listing 2's parent classes reflect those relationships. The details of method resolution are beyond this article's scope. But be wary of relying on the value of this and super (in its various forms) within mixin methods.

Use of category classes or ExpandoMetaClass doesn't affect inheritance, because you make changes to the class rather than mix in a distinct new behavior. A drawback is that those changes aren't identifiable as a distinct categorical artifact. If I use category classes or ExpandoMetaClass to add the same three methods to multiple classes, no specific code artifact (such as an interface or class signature) identifies the commonality that now exists. The advantage of a mixin is that Groovy treats everything that uses the mixin as a category.

One of the implementation annoyances for category classes is the strict class structure. You must use all static methods, each of which accepts at least a single parameter, to represent the type that's under augmentation. Metaprogramming is most useful when it eliminates such boilerplate code. The advent of the @Mixin annotation made it much easier to create categories and mix them into classes. Listing 3 (an excerpt from the Groovy documentation), illustrates the synergy between categories and mixins:

Listing 3. Combining categories and mixins
interface Vehicle {
    String getName()
}

@Category(Vehicle) class Flying {
    def fly() { "I'm the ${name} and I fly!" }
}

@Category(Vehicle) class Diving {
    def dive() { "I'm the ${name} and I dive!" }
}

@Mixin([Diving, Flying])
class JamesBondVehicle implements Vehicle {
    String getName() { "James Bond's vehicle" }
}

assert new JamesBondVehicle().fly() ==
       "I'm the James Bond's vehicle and I fly!"
assert new JamesBondVehicle().dive() ==
       "I'm the James Bond's vehicle and I dive!"

in Listing 3, I create a simple Vehicle interface and two category classes (Flying and Diving). The @Category annotation attends to the boilerplate code requirements. After I define the categories, I mix them into a JamesBondVehicle, thereby attaching both behaviors.

The intersection of categories, ExpandoMetaClass, and mixins in Groovy is the inevitable consequence of aggressive language evolution. The three techniques overlap significantly, but each has sweet spots that only it can handle best. If Groovy were redesigned from the ground up, the authors would likely consolidate many of the three techniques' features into a single mechanism.

Scala traits

Scala implements code reuse through traits, which are a core language feature that's similar to mixins. Traits in Scala are stateful — they can include both methods and fields — and they play the same instanceof role that interfaces play in the Java language. Traits and mixins both solve many of the same problems, but traits are supported by more language rigor.

In "Common ground in Groovy, Scala, and Clojure, Part 1," I used a complex-number class to illustrate operator overloading in Scala. I didn't implement the Boolean comparison operators in that class because Scala's built-in Ordered trait renders the implementation trivial. Listing 4 shows an improved complex-number class that takes advantage of the Ordered trait:

Listing 4. Comparable complex numbers
final class Complex(val real: Int, val imaginary: Int) extends Ordered[Complex] {
  require (real != 0 || imaginary != 0)

  def +(operand: Complex) =
      new Complex(real + operand.real, imaginary + operand.imaginary)

  def +(operand: Int) =
    new Complex(real + operand, imaginary)

  def -(operand: Complex) =
    new Complex(real - operand.real, imaginary - operand.imaginary)

  def -(operand: Int) =
    new Complex(real - operand, imaginary)


  def *(operand: Complex) =
      new Complex(real * operand.real - imaginary * operand.imaginary,
          real * operand.imaginary + imaginary * operand.real)

  override def toString() =
      real + (if (imaginary < 0) "" else "+") + imaginary + "i"

  override def equals(that: Any) = that match {
    case other : Complex => (real == other.real) && (imaginary == other.imaginary)
    case _ => false
  }

  override def hashCode(): Int =
    41 * ((41 + real) + imaginary)

  def compare(that: Complex) : Int = {
    def myMagnitude = Math.sqrt(this.real ^ 2 + this.imaginary ^ 2)
    def thatMagnitude = Math.sqrt(that.real ^ 2 + that.imaginary ^ 2)
    (myMagnitude - thatMagnitude).round.toInt
  }
}

I don't implement the >, <, <=, and >= operators in Listing 4, yet I can invoke them on complex-number instances as shown in Listing 5:

Listing 5. Testing comparisons
class ComplexTest extends FunSuite {

  test("comparison") {
    assert(new Complex(1, 2) >= new Complex(3, 4))
    assert(new Complex(1, 1) < new Complex(2,2))
    assert(new Complex(-10, -10) > new Complex(1, 1))
    assert(new Complex(1, 2) >= new Complex(1, 2))
    assert(new Complex(1, 2) <= new Complex(1, 2))
  }

}

No single mathematically defined technique exists for comparing complex numbers, so in Listing 4 I use a generally accepted algorithm to compare the numbers' magnitudes. I extend the class definition with the Ordered[Complex] trait, which mixes in the Boolean operators for the parameterized class. For the trait to function, the injected operators must compare two complex numbers, which is the purpose of the compare() method. If you try to extend the Ordered trait but don't supply the required methods, a compiler message notifies you that your class must be declared abstract because required methods are missing.

Traits have two clearly defined roles in Scala: enriching interfaces and performing stackable modifications.

Enriching interfaces

When Java developers design interfaces, they face a conundrum that hinges on convenience: Should you create a rich interface that contains many methods, or a thin interface that has just a few? A rich interface is more convenient for its consumers because it provides a wide palette of methods, but the sheer number of methods makes the interface more difficult to implement. Thin interfaces have the converse problem.

Traits solve the rich-versus-thin dilemma. You can create core functionality in a thin interface, then augment it with traits to provide richer functionality. For example, in Scala, the Set trait implements a set's shared functionality, and the subtrait that you choose — mutable or immutable— determines if the set is mutable or not.

Stackable modifications

The other common use of traits in Scala is stackable modifications. With traits, you can change existing methods and add new ones, and super provides access to chain back to the previous traits' implementation.

Listing 6 illustrates stackable modifications with a number queue:

Listing 6. Building stackable modifications
abstract class IntQueue {
  def get(): Int
  def put(x: Int)
}

import scala.collection.mutable.ArrayBuffer

class BasicIntQueue extends IntQueue {
  private val buf = new ArrayBuffer[Int]
  def get() = buf.remove(0)
  def put(x: Int) { buf += x }
}

trait Squaring extends IntQueue {
  abstract override def put(x: Int) { super.put(x * x) }
}

In Listing 6, I create a simple IntQueue class. Then I build a mutable version that includes an ArrayBuffer. The Squaring trait extends any IntQueue and automatically squares values when they are inserted into the queue. The call to super within the Squaring trait provides access to the preceding traits in the stack. As long as each overridden method except the first one calls super, the modifications stack atop one another, as shown in Listing 7:

Listing 7. Building stacked instances
object Test {
  def main(args: Array[String]) {
    val queue = (new BasicIntQueue with Squaring)
    queue.put(10)
    queue.put(20)
    println(queue.get()) // 100
    println(queue.get()) // 400
  }
}

The use of super in Listing 6 illustrates an important difference between traits and mixins. Mixins — because you (literally) mix them in after the creation of the original class — must address the potential ambiguity of the current location within the class hierarchy. Traits are linearized as the class is created; the compiler resolves the question of what super is, without ambiguity. Complex, strictly defined rules (which are beyond this article's scope) govern how linearization works in Scala. Traits also solve the diamond problem for Scala. No ambiguity is possible when Scala tracks method origin and resolution, because the language defines explicit rules to handle the resolution.

Conclusion

In this installment, I explored the similarities and differences between mixins (in Groovy) and traits (in Scala). Mixins and traits offer many similar features, but the implementation details differ in important ways that illustrate differing language philosophies. In Groovy, mixins exist as annotations and use the powerful metaprogramming capabilities that AST transformations afford. Mixins, category classes, and the ExpandoMetaClass all overlap in functionality, with slight (but important) differences. Traits in Scala such as Ordered form a core language feature that much of the built-in functionality in Scala relies on.

In the next installment, I cover currying and partial application in the Java.next languages.


Downloadable resources


Related topics

  • Scala: Scala is a modern, functional language that runs on the JVM.
  • Clojure: Clojure is a modern, functional Lisp that runs on the JVM.
  • Multiple inheritance: Learn more in this Wikipedia article about why the diamond problem arises in multiple-inheritance languages.
  • Mixin: Read more about mixins (including the origin of the term) and about the Flavors language on Wikipedia.
  • "Functional thinking": Explore functional programming in Neal Ford's column series on developerWorks.
  • "Language designer's notebook": In this developerWorks series, Java Language Architect Brian Goetz explores some of the language design issues that have presented challenges for the evolution of the Java language in Java SE 7, Java SE 8, and beyond.

Comments

Sign in or register to add and subscribe to comments.

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=1
Zone=Java development
ArticleID=945489
ArticleTitle=Java.next: Mixins and traits
publish-date=09192013