Scala from the ground up

Traits and shared behaviour

A trait that states what a type can do, one method written once inside it, two classes that fill in the blanks, and a list of the trait type printed by one loop.

Part 10 of 12

Advanced18 min readBack to Courses

A trait is a type with methods

A trait says what something can do. trait Shape with def name: String and def area: Double and no bodies is a promise: any class that extends it has to supply those two. The trait itself is a type, so it can be used for a list, a parameter or a return value.

Abstract means write it yourself; concrete means you are given it. def report: String = name + " area " + area is the concrete kind — it calls the two abstract methods, so the shared sentence works for every shape that fills in the blanks, and no class has to repeat the wording.

Two classes, one type

class Square(val side: Double) extends Shape takes the trait and answers both questions with the field. class Rect(val w: Double, val h: Double) extends Shape does the same with two fields. The classes are otherwise unrelated, and both are Shapes, which is the point: the type in common is what the rest of the program is written against.

List(square, rect) has the type List[Shape], and one loop over it prints both reports. The loop does not ask which class it is holding, and it never names either of them: each value answers with its own methods, and the report it inherits does the rest.

Why the shared method is the whole point

Without the concrete method, each class would have written the same sentence and the copies would drift: one with a colon, one with brackets, one with the number first. With it, the wording lives once and the classes carry only what is genuinely different — their fields and their arithmetic.

A Double prints with its decimal part, so an area of nine arrives as 9.0. That is the type being honest about what it holds rather than a formatting mistake, and the last chapter is where output gets shaped into columns instead.

The trait is the type the caller sees

A class that extends a trait is a subtype of it. That is what lets List(square, rect) have the type List[Shape]: both values are shapes, whatever else they are. The loop is written against the trait and calls report, name and area through it, and each value answers with the implementation from its own class.

The call is decided at run time by the object, not by the variable. This is dynamic dispatch: the compiler checks that Shape promises a report method, and the virtual machine finds the version that belongs to the actual class. Because report is written once in the trait and calls the abstract name and area, one sentence serves every shape that fills in those two answers.

An abstract member is a promise with no body; a concrete member is one with a body that every subclass inherits. A class may extend one class and mix in several traits with with, which is how a type collects small pieces of behaviour. The compiler rejects a class that leaves an abstract member unanswered, so the promise cannot be forgotten.

Several traits, one class

A class can extend only one class, but it can mix in several traits with with. That is the reason small behaviours are written as traits: a Shape trait and a Named trait can both be added to one class, while two parent classes cannot. The trait carries the type and the shared method; the class carries the fields.

A trait may also declare a val or a var, but a val in a trait is fixed when the class is built, and an abstract one has to be supplied by the class. Prefer the abstract def for anything that depends on a field, so the class decides when it is computed.

Three traps, all caught by the compiler: forgetting override when a class replaces a concrete trait method, writing def area: Double = { ... } whose last line is a println and so has type Unit, and calling new on the trait itself. A trait is a type; only a class can be instantiated.

Try these next

  • Add a Triangle class with base and height, and put one in the shape list.
  • Give the trait a second concrete method that prints a label, and call it on both shapes.
  • Make a class that extends the trait but forgets area, then read the compiler message.

One trait, two classes

trait Shape {
  def name: String
  def area: Double
  def report: String = name + " area " + area
}

class Square(val side: Double) extends Shape {
  def name: String = "square"
  def area: Double = side * side
}

class Rect(val w: Double, val h: Double) extends Shape {
  def name: String = "rect"
  def area: Double = w * h
}

val shapes = List(new Square(3.0), new Rect(2.0, 5.0))
for (s <- shapes) {
  println(s.report)
}

One loop, two shapes

Run it and note that both reports came from the trait. Then add a `Triangle` class with a base and a height, put one in the list, and watch the loop print it without any change.

Output
Click "Run" to execute your code

Invoices and salaries

Run it and follow both entries through the same `line` method. Then add a `Refund` class with a negative amount, and see what the total becomes.

Output
Click "Run" to execute your code

Put only the method names in the trait, and the shared bodies after them: def name: String is the promise and def report: String = ... is the gift. Everything a subclass must answer is visible at the top of the file.

Check what you learned

One question at a time. A wrong answer explains itself — take another go.

Part 1 of 5

What must a class that extends the trait do?