Traits: Adding Concrete Behaviors

In these examples we start adding concrete behaviors to traits, and then use those traits as mixins.

Example

If you want to model animals that have tails — and dozens of other features — you might start by modeling the tail with a trait. This time we’ll make the trait methods concrete rather than abstract:

trait HasTail:
    def startTail = "Tail started"
    def stopTail = "Tail stopped"

Those are concrete methods because they have an implementation. Technically they are defined as two methods that yield String results, so you can show the method return type as well:

trait HasTail:
    def startTail: String = "Tail started"
    def stopTail: String = "Tail stopped"

Similarly, methods related to legs might also have a default implementation:

trait HasLegs:
    def walk = "Walking"
    def run = "Running"
    def standStill = "Standing still"

Then when you get to speaking, you might want to leave those methods abstract, because different implementors will implement a speak method in different ways:

trait CanSpeak:
    def speak: String

Jumping ahead a little bit, traits in Scala 3 can also have parameters, just like classes:

trait Pet(name: String)

Given all these traits, you can now create classes by extending those traits, and implementing the methods as desired:

class Dog(name: String) extends Pet(name), HasTail, HasLegs, CanSpeak:
    def speak = "Woof"
    override def stopTail: String = "Tail stopped (reluctantly)"

class Cat(name: String) extends Pet(name), HasTail, HasLegs, CanSpeak:
    def speak = "Meow"

val d = Dog("Fido")
d.walk
d.run
d.standStill
d.speak

val c = Cat("Morris")

Complete example

Here’s a complete example from the video:

trait HasTail:
    def startTail: String = "Tail started"
    def stopTail: String = "Tail stopped"

trait HasLegs:
    def walk = "Walking"
    def run = "Running"
    def standStill = "Standing still"

trait CanSpeak:
    def speak: String

trait Pet(name: String):
    def myName: String = s"My name is $name."

class Dog(name: String) extends Pet(name), HasTail, HasLegs, CanSpeak:
    def speak = "Woof"
    override def stopTail: String = 
        "Tail stopped (reluctantly)"

@main def main =
    val d = Dog("Fido")
    println(d.walk)
    println(d.run)
    println(d.standStill)
    println(d.speak)
    println(d.myName)

Real-world example

For this example I begin with my best-selling book, Functional Programming, Simplified.

It’s very common to have traits that declare abstract methods. To make a method abstract, just declare its name and its value (i.e., its return type). Here are some examples of traits with abstract methods that I later combine into a complete class:

import java.time.LocalDate

trait Product:                                    // 1st
    def cost: Double
    def price: Double
    def sku: String
    def title: String
    def description: String

trait BookTrait extends Product:                  // 3rd (tie)
    def title: String
    def mainAuthor: String
    def otherAuthors: Seq[String]
    def publicationDate: LocalDate
    def language: String

trait PaperbackBookTrait extends BookTrait:       // 2nd
    def pageCount: Int
    def isbn: String

trait EBookTrait extends BookTrait:               // 3rd (tie)
    def fileSizeInBytes: Int
    def hasEnhancedTypesetting: Boolean
    def hasFreePreview: Boolean

class PaperbackBook(                              // 4th
    val title: String,
    val mainAuthor: String,
    val otherAuthors: Seq[String],
    val isbn: String,
    val pageCount: Int,
    val publicationDate: LocalDate,
    val language: String
) extends PaperbackBookTrait:
    def cost = ???
    def price = ???
    def sku = ???
end PaperbackBook

val scalaCookbook = PaperbackBook(                // 5th
    title = "Scala Cookbook",
    mainAuthor = "Alvin Alexander",
    otherAuthors = Seq[String](),
    isbn = "1492051543",
    pageCount = 820,
    publicationDate = LocalDate.parse("2020-12-10"),
    language = "English"
)

Note:A key in the Scala design process and domain modeling is that the traits are granular and single-purpose, and we later combine them to create a complete class.