Java.next: Mixins and traits

Mix new behaviors into Groovy and Scala classes

The Java™ language's primary paradigm — object orientation with single inheritance — effectively models most but not all programming problems. The Java.next languages extend this paradigm in various ways, including mixins and traits. This Java.next installment defines the mechanisms that mixins and traits share, and it delves into the subtle differences between mixins in Groovy and traits in Scala.

Share:

Neal Ford, Director / Software Architect / Meme Wrangler, ThoughtWorks

Photo of Neal FordNeal Ford is Director, Software Architect, and Meme Wrangler at ThoughtWorks, a global IT consultancy. He is also the designer and developer of applications, instructional materials, magazine articles, courseware, and video/DVD presentations, and he is the author or editor of books spanning a variety of technologies, including the most recent Presentation Patterns. He focuses on designing and building large-scale enterprise applications. He is also an internationally acclaimed speaker at developer conferences worldwide. Check out his website.



19 September 2013

Also available in Chinese Russian Japanese

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 Resources). 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.)

About this series

The Java legacy will be the platform, not the language. More than 200 languages run on the JVM, and it's inevitable that one of them will eventually supplant the Java language as the best way to program the JVM. This series explores three next-generation JVM languages: Groovy, Scala, and Clojure, comparing and contrasting new capabilities and paradigms, to provide Java developers a glimpse into their own near future.

Mixins

Ice cream inspiration

The mixin concept originated with the Flavors language (see Resources). The concept was inspired by an ice cream shop near the office where language development occurred. The ice cream parlor offered plain flavors of ice cream with any additional "mix-ins" (crumbled candy bars, sprinkles, nuts, and so on) that customers wanted.

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.

Resources

Learn

  • Groovy: Groovy is a dynamic variant of the Java language, with updated syntax and capabilities.
  • 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.
  • developerWorks Java technology zone: Find hundreds of articles about every aspect of Java programming.

Discuss

  • Get involved in the developerWorks community. Connect with other developerWorks users while exploring the developer-driven blogs, forums, groups, and wikis.

Comments

developerWorks: Sign in

Required fields are indicated with an asterisk (*).


Need an IBM ID?
Forgot your IBM ID?


Forgot your password?
Change your password

By clicking Submit, you agree to the developerWorks terms of use.

 


The first time you sign into developerWorks, a profile is created for you. Information in your profile (your name, country/region, and company name) is displayed to the public and will accompany any content you post, unless you opt to hide your company name. You may update your IBM account at any time.

All information submitted is secure.

Choose your display name



The first time you sign in to developerWorks, a profile is created for you, so you need to choose a display name. Your display name accompanies the content you post on developerWorks.

Please choose a display name between 3-31 characters. Your display name must be unique in the developerWorks community and should not be your email address for privacy reasons.

Required fields are indicated with an asterisk (*).

(Must be between 3 – 31 characters.)

By clicking Submit, you agree to the developerWorks terms of use.

 


All information submitted is secure.

Dig deeper into Java technology on developerWorks


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