Mike Slinn

Scala Traits

— Draft —

Published 2014-02-08. Last modified 2016-01-23.
Time to read: 4 minutes.

This is the first of three lectures on Scala Traits.

Scala traits can be pure, which resemble Java 7’s interfaces, or they can have implementations. Traits are ‘mixed in’ to form classes that have a kind of multiple inheritance without the dreaded ‘diamond inheritance’ antipattern that C++ suffers from.

We say that traits are ’mixed in’. Multiple traits can be mixed into a class or object. Trait constructors cannot accept parameters.

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

Pure Trait

A pure trait has no implementation and is equivalent to a Java Interface. Note that I have defined the property in the Checkable trait below with def, even though you might expect it to normally only require a val. We discussed why this is a good practice in the Setters, Getters and the Uniform Access Principle lecture.

Scala code
import java.sql.Date

trait Checkable { def preFlight: Boolean }

class Course(
  startDate: Date = new Date(System.currentTimeMillis),
  override val preFlight: Boolean = false // override is unnecessary but legal
) extends Checkable {
  override val toString = s"startDate=$startDate, preFlight=$preFlight"
}

def isReady(checkable: Checkable): Boolean = checkable.preFlight

It is up to you to decide if you want to decorate val preFlight with override or not when overriding an abstract method or property. I will not do so any further. Now let’s create some instances of the class Course which mixes in the pure trait Checkable.

Scala REPL
scala> val course1 = new Course(Date.valueOf("2014-01-01"), true)
course1: Course = Course(2014-01-01,true)
scala>
val course2 = new Course() course2: Course = Course(2014-01-05,false)

You can view an instance of a class as an instance of any of its traits. For example, lets pass a Course instance to isReady, which accepts a Checkable parameter.

Scala REPL
scala> isReady(course1)
res0: Boolean = true 

Trait with Implementation

A trait with an implementation is similar to an abstract class, however traits cannot have a constructor that takes arguments. Because HasId is not a pure trait in the following code, classes that mix in this trait must specify override if id is to be mixed in as a property instead of a method.

Scala code
import java.sql.Date
trait Checkable { def preFlight: Boolean }
trait HasId { def id: Long = 0L }
class Lecture( override val id: Long = 0L, startDate: Date = Date.valueOf("2014-01-01"), val preFlight: Boolean = false ) extends Checkable with HasId { override val toString = s"id=$id, startDate=$startDate, preFlight=$preFlight" }

Now let’s create some instances.

Scala REPL
scala> val lecture1 = new Lecture(1L, preFlight=true)
lecture1: Lecture = Lecture(1,2014-01-01,true)
scala>
val lecture2 = new Lecture() lecture2: Lecture = Lecture(0,2014-01-05,false)

Referring to Supertypes

If you have an instance of a type that is derived from a complex hierarchy of supertypes, you can refer to it as if it were any of the supertypes. To show what I mean, here are the definitions of types Base, First and Holder.

Scala REPL
scala> trait Base { val param1: Int }
defined trait Base
scala>
abstract class First(val param2: String) defined class First
scala>
case class Holder(override val param1: Int, override val param2: String) extends First(param2) with Base defined class Holder

We can create an instance of Holder:

Scala REPL
scala> val bottles: Holder = new Holder(99, "Bottles of beer")
bottles: Holder = Holder(99,Bottles of beer) 

This instance can be accessed as any of the supertypes:

Scala REPL
scala> val base: Base = bottles
base: Base = Holder(99,Bottles of beer)
scala>
val first: First = bottles first: First = Holder(99,Bottles of beer)
scala>
base.param1 res24: Int = 99
scala>
first.param2 res25: String = Bottles of beer

You can run this example by typing:

Shell
$ sbt "runMain HeirGround"

Extending Multiple Traits

Objects and classes can extend multiple traits. The first trait or class being extended is mixed in with the keyword extends. All other traits are mixed in with the keyword with. The order of the traits is significant – in the following example, the type Shuttle extends Spacecraft with ControlCabin with PulseEngine is not equivalent to the type Shuttle extends Spacecraft with PulseEngine with ControlCabin. Not only does the order of the mixin determine the type, it also determines the behavior of the resulting class.

There are many traits and a few classes that are combined to make spaceships of various types and capabilities. Many of these traits define a method called speedUp. The order of the mixin matters, and the implementation of the speedUp method will be provided by the rightmost trait with a concrete implementation of that method.

Let’s walk through the code.

  • The enclosing object, BoldlyGo, creates an Explorer, which we will look at in a moment.
  • There is an abstract class called Spacecraft, which defines a method called engage that accepts no parameters and does not return anything. Clearly engage is only useful for side effects, so it is written with empty parentheses, which is hint to the reader that this method is ’side-effecty’.
  • The Bridge trait is not a pure trait because its engage method has an implementation. Its speedUp method is undefined, but because it is written with empty parentheses we expect that implementations will have side effects.
  • The Engine trait also defines a speedUp method.
  • The PulseEngine trait extends the Engine trait, and it:
    • Provides an implementation for the speedUp method declared in Engine, and the implementation’s side effect is to set the value of currentPulse.
    • Defines a mutable variable of type Int called currentPulse.
    • Defines an abstract method called maxPulse that looks like a getter, because it does not receive any parameters and returns a value, and we don’t expect side effects because it was not defined using paretheses.
  • The ControlCabin trait provides an implementation for engage and declares an increaseSpeed method.
  • The Shuttle class extends the Spacecraft abstract superclass, mixes in ControlCabin and PulseEngine, and provides a concrete implementation of maxPulse and increaseSpeed. Scala only allows one class or abstract class to be extended when defining a new class, and if mixing in one or more traits as well, the superclass must be mentioned first using the extends keyword. If a defined class does not inherit from a superclass and only extended traits, then the first trait mixed in would be prefaced with the keyword extends and the names of the other traits would follow, separated by the keyword with. increaseSpeed invokes the speedUp method from PulseEngine, not the method of the same name from ControlCabin because PulseEngine was mixed in to the right of ControlCabin.
  • The WarpEngine trait subclasses Engine, adding the new abstract method maxWarp, which looks like a getter, the mutable currentWarp variable and a concrete implementation of the speedUp method from Engine.
  • Class Explorer extends the abstract class Spacecraft, which must be mentioned before any traits are mixed in. The Bridge and WarpEngine traits are then mixed in. Explorer provides a concrete implementation of maxWarp declared by WarpEngine and a concrete implementation of speedUp declared by Engine.
  • The singleton object Defiant is an instance of Explorer that also extends Spacecraft but mixes in the ControlCabin and WarpEngine traits instead of the Bridge and WarpEngine traits that the Explorer class mixes in.
Scala code
object BoldlyGo extends App {
  val explorer = new Explorer
  println(explorer)
abstract class Spacecraft { def engage(): Unit }
trait Bridge { def speedUp(): Unit def engage(): Unit = { speedUp(); speedUp(); speedUp() } }
trait Engine { def speedUp(): Unit }
trait PulseEngine extends Engine { var currentPulse = 0 def maxPulse: Int def speedUp(): Unit = if (currentPulse < maxPulse) currentPulse += 1 }
trait ControlCabin { def increaseSpeed(): Unit def engage(): Unit = increaseSpeed() }
class Shuttle extends Spacecraft with ControlCabin with PulseEngine { val maxPulse = 10 def increaseSpeed(): Unit = speedUp() }
trait WarpEngine extends Engine { def maxWarp: Int var currentWarp: Int = 0 def toWarp(x: Int): Unit = if (x < maxWarp) currentWarp = x }
class Explorer extends Spacecraft with Bridge with WarpEngine { val maxWarp = 10 def speedUp(): Unit = toWarp(currentWarp + 1) }
object Defiant extends Spacecraft with ControlCabin with WarpEngine { val maxWarp = 20 def increaseSpeed() = toWarp(10) def speedUp(): Unit = toWarp(currentWarp + 2) } }

Traits Should Not Extend Classes

Just because you can do something, does not mean that you should do it. Extending traits with classes would allow multiple inheritance, which has many problems. Here is one example of an error that results from attempting multiple inheritance.

Scala REPL
scala> class Animal
defined class Animal
scala>
trait Furry extends Animal defined trait Furry
scala>
class Plant defined class Plant
scala>
trait Leafy extends Plant defined trait Leafy scala> trait Chimera extends Leafy with Furry <console>:11: error: illegal inheritance; superclass Plant is not a subclass of the superclass Animal of the mixin trait Furry trait Chimera extends Leafy with Furry ^

* 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.