Mike Slinn

Stackable Traits and Abstract Override

— Draft —

Published 2014-02-19. Last modified 2014-12-16.
Time to read: 3 minutes.

Traits can be ‘stacked’, or mixed together, and this lecture describes how stacked traits work. This lecture also shows how traits can also be used to modify the behavior of any JVM class.

The source code for this lecture is provided in courseNotes/src/main/scala/Traits2.scala.

Stackable Traits

In Scala, just as in Java, super for subclasses is determined statically. Scala traits are much more flexible, and this lecture will explore two ways to work with Scala traits that are beyond anything that Java can do.

Methods defined in traits can override concrete methods defined in classes or traits that they extend. To do this, the trait’s overriding methods must be decorated with abstract override. This is possible because for traits, super is dynamically determined according to the order in which the trait was mixed in. When a method is called on a trait, the trait furthest right in the mix which has that method provides the implementation. If this method calls super then it invokes the method on the trait on its left, and so on. In the following example, the Color trait is not abstract, so the Red, White and Blue traits can superimpose their two abstract override methods: color and paint on concrete implementations. The paint methods are written in such a way that you can see how Scala applies the overrides.

  object WhichTrait extends App {
    trait Color {
      def color: String = "Unknown"
      def paint(highlight: String): String = s"$color with $highlight highlights"
    }

    trait Red extends Color {
      override val color: String = "red"
      override def paint(highlight: String): String = super.paint(s"pink and $highlight")
    }

    trait White extends Color {
      override val color: String = "white"
      override def paint(highlight: String): String = super.paint(s"light yellow and $highlight")
    }

    trait Blue extends Color {
      override val color: String = "blue"
      override def paint(highlight: String): String = super.paint(s"bluegreen and $highlight")
    }

    val red = new Red{}
    println(s"""red.paint=${red.paint("polkadot")}""")

    val redWhite = new Red with White
    println(s"""redWhite.paint=${redWhite.paint("polkadot")}""")

    val redWhiteBlue = new Red with White with Blue
    println(s"""redWhiteBlue.paint=${redWhiteBlue.paint("polkadot")}""")

    class Concrete extends Red with White with Blue
    val concrete = new Concrete
    println(s"""concrete.paint=${concrete.paint("polkadot")}""")
  }

You can run this program by typing.

Scala REPL
$ scala> sbt "runMain WhichTrait"

Output is.

  red.paint=red with pink and polkadot highlights
  redWhite.paint=white with pink and light yellow and polkadot highlights
  redWhiteBlue.paint=blue with pink and light yellow and bluegreen and polkadot highlights
  concrete.paint=blue with pink and light yellow and bluegreen and polkadot highlights

The red variable contains an instance of an anonymous subclass of the Red trait. Similarly, the redWhite variable contains an instance of an anonymous subclass of the Red and White traits mixed together. The redWhiteBlue variables contains an instance of an anonymous subclass of the Red, White and Blue traits. The concrete variable contains an instance of the Concrete class, which is also a mix of the Red, White and Blue traits. Although the behavior of redWhiteBlue is identical to that of concrete, there is only one class definition for all Concrete instances. In contrast, each time you write Red with White with Blue a new abstract class definition is created.

When creating the red variable by using the Red trait alone, we need to include an empty body to create an anonymous subclass of Red, like this.

new Red{}
However, when mixing in two or more traits, like the following, we don’t have to provide an empty body.
  new Red with White
This is because mixing traits automatically creates an anonymous class, unless the resulting type is given a name.

Notice the order of the colors that are printed out by redWhiteBlue.paint and concrete.paint.

  1. The invocation passes polkadot to concrete.paint.
  2. The rightmost trait, Blue, prepends bluegreen and invokes super.paint.
  3. The next rightmost trait, White, prepends light yellow and invokes super.paint.
  4. The next rightmost trait, Red, prepends pink and invokes super.paint.
  5. The Color trait, which is the superclass of the Red trait, prepends the value of color. Unlike the paint methods, the color methods in each trait do not call super, so that property is consistent for all traits, and it set by the right-most trait that was mixed in.

Abstract Override

The following example defines a trait that extends the mutable java.util.Set[String], which is a Java interface that uses generics. Scala also supports generics, and we will cover that topic in the Parametric Types lecture of the Intermediate Scala course. This code is somewhat ugly because Scala generics are not used.

The IgnoredCaseSet trait’s overriding add method converts any incoming Strings to lower case before adding it to the set. Other types of objects are merely added to the set without modification.

This code uses isInstanceOf to test if the runtime type of obj is a String, and asInstanceOf to typecast obj to String if that is its runtime type. Later in this course we will learn how to use the match keyword and then we won’t need to use isInstanceOf any more.

  trait IgnoredCaseSet extends java.util.Set[Object] {
    abstract override def add(obj: Object): Boolean =
      if (obj.isInstanceOf[String]) super.add(obj.asInstanceOf[String].toLowerCase) else super.add(obj)

    abstract override def contains(obj: Object): Boolean =
      if (obj.isInstanceOf[String]) super.contains(obj.asInstanceOf[String].toLowerCase) else super.contains(obj)
  }

  class MySet extends java.util.HashSet[Object] with IgnoredCaseSet

Java HashSet implements Set, which is why we could subclass HashSet by mixing in IgnoredCaseSet above. Let’s create a MySet instance that contains Strings, and add mixed case Strings to it. We will then use the trait’s overriding contains method to determine if the set contains the equivalent lower case String.

Scala REPL
scala> val set = new MySet()
set: MySet = []
scala> %}set.add("One") res3: Boolean = true
scala> %}set.add("Two") res4: Boolean = true
scala> %}set.add("Three") res5: Boolean = true
scala> %}set.contains("hi there") res6: Boolean = false
scala> %}mySet.contains("two") res7: Boolean = true

* indicates a required field.

Please select the following to receive Mike Slinn’s newsletter:

You can unsubscribe at any time by clicking the link in the footer of emails.

Mike Slinn uses Mailchimp as his marketing platform. By clicking below to subscribe, you acknowledge that your information will be transferred to Mailchimp for processing. Learn more about Mailchimp’s privacy practices.