Mixins in Scala

Versions: Scala 2.12.1

Multiple inheritance can lead to a lot of issues and one of the most known is the diamond problem where the compiler doesn't know which of inherited methods use. However in Scala we can use another structure to compose a class with several different classes, keeping us far away of the diamond problem. This structure is called mixin.

This post defines Scala's mixins. Its first section shows it from a big picture. The second part explains how mixins can be simply implemented in Scala. The next one talks about a way Scala resolves the quoted diamond problem. The last section contains a code showing a practical use case of mixins in a Scalatest.

Mixin definition

A mixin can be described in different terms. One of the most common are:

Mixins and Scala

Mixins in Scala are the traits that are chained together through with keyword as shown in the following snippet:

trait A
trait B
class Letter

class ABLetter extends A with B

// The mixin can also be created on the fly
val abLetter = new Letter with A with B

As you can also note, the class composed of mixins (let's call it "mixed class" for simplicity) extends another class. This inheritance step is required and we can't build a class composed only of mixins.

In the situation where the mixed class includes 2 traits having the same method, the code won't compile. It's illustrated in the following example:

class X
trait A {
  def get: String = "A"
}
trait B {
  def get: String = "B"
}

class C extends X with A with B { }

The compilation for such code will fail with:

Error:(88, 7) class C inherits conflicting members:
method get in trait A of type => String  and
method get in trait B of type => String

Scala mixin and diamond problem

The diamond problem in Scala is solved pretty simply. The compiler takes the rightmost implementation of given method as the implementation used in mixed class. For instance in a class new MyClass extends ParentClass with Mixin1 with Mixin2 where Mixin1 and Mixin2 inherits from the same parent, the methods implemented in Mixin2 will be used. An exception happens however if the rightmost class doesn't provide the implementation for given method. If it's the case, the 2nd rightmost trait is used (Mixin1). If it also doesn't provide the behavior it's the 3rd mixin and so on.

Diamond problem

The diamond problem can be easily illustrated in the following image:

As you can see the class EnglishPerson extends 2 implementations of the same interface. Thus both of them implement the speak() method. If the programming language doesn't have the resolution rules for that case, the code won't work. Otherwise, as in the case of Scala, the resolution rule will be applied on compilation time to decide which of these 2 implementations should be used.

The following tests show how the diamond problem is handled by Scala:

  behavior of "mixin class in diamond problem context"

  it should "take the behavior from the rightmost mixed trait" in {
    abstract class Letter {
      def get: String
    }
    trait A extends Letter {
      override def get: String = "A"
    }
    trait B extends Letter {
      override def get: String = "B"
    }
    class ABLetter extends Letter with A with B {
      override def get: String = super.get
    }

    val abLetter = new ABLetter

    abLetter.get shouldEqual "B"
  }
  it should "take the behavior from the 2nd rightmost mixed trait" in {
    // As explained in the post, it happens because trait B doesn't implement
    // the get method
    // If the implementation was missing in trait A, then the ABLetter class should provide
    // an implementation itself. Otherwise the code wouldn't compile.
    abstract class Letter {
      def get: String
    }
    trait A extends Letter {
      override def get: String = "A"
    }
    trait B extends Letter {
    }
    class ABLetter extends Letter with A with B {
      override def get: String = super.get
    }

    val abLetter = new ABLetter

    abLetter.get shouldEqual "A"
  }

You can also notice that the ABLetter class extends the same base class as the mixin traits. If it wasn't the case, the code wouldn't compile and the following error would be printed:

Error:(41, 36) illegal inheritance; superclass OtherLetter
 is not a subclass of the superclass Letter
 of the mixin trait A
    class ABLetter extends OtherLetter with A with B {

Scala mixin real world example

A good real world examples of Scala's mixin are Scalatest specifications. As shown in the post about ScalaTest and testing styles, the mixins are often used to compose reusable test base classes providing a common initialization logic. An example of such implementation is defined in the following test case:

class ScalatestExampleTest extends ExternalStorageSpec {

  "an example" should "show how the Scalatesdt mixin works" in {
    println("===Test#1===")
  }

  "another example" should "show how the Scalatest mixin works" in {
    println("===Test#2===")
  }

}

class ExternalStorageSpec extends FlatSpec with BeforeAndAfterAll with BeforeAndAfter {
  override def beforeAll(): Unit = {
    println("Init embedded DB")
  }

  override def afterAll(): Unit = {
    println("Shutdown embedded DB")
  }

  before {
    println("Add test data")
  }

  after {
    println("Remove test data")
  }
}

Once executed we should see the following output:

Init embedded DB
Add test data
===Test#1===
Remove test data
Add test data
===Test#2===
Remove test data
Shutdown embedded DB

This special kind of inheritance can help a lot to compose behavior-oriented classes. As told in the first section, the mixins prefer "behave as" rather than "is a" relationship and thus we can consider them as a special kind of composition. More specifically in Scala the mixins are defined thanks to with keywords followed by the traits used in the composition. Scala handles the multiple inheritance diamond problem pretty simply because it uses the rightmost defined method as the one used in the mixed class.