Mike Slinn

Self Types vs Inheritance

— Draft —

Published 2014-02-08. Last modified 2016-10-11.
Time to read: 6 minutes.

This lecture discusses the use of self types with traits to implement a requires-a relationship between types, and gives some example of how self types can be used. Self types allow a dependency to be expressed within a trait, that when satisfied allows the trait to make use of code defined in another type – without polluting the trait with the implementation details of the other type.

This lecture compares self types with both composition (has-a relations) and inheritance (is-a relations). It includes a comprehensive example of using self types, inheritance and composition in object-oriented modeling.

In the previous lectures, when we defined a trait, its been totally independent of any class we might mix it in with. But what if we want our trait to make use of functionality defined in the type its being mixed in with – either a class or a trait? In the following code we have defined a Person class that can speak.

If the self type is a pure trait, implementation details of the self type are not exposed
  class Person(name: String) {
    def speak(feelings: String) = println(feelings)
  }

Self Types

Now suppose we want provide emotion-related traits that use the Person’s speak method, that we can mix in with the Person class, to provide a mode of expression – happy, sad, angry and so on. For example, in the following code we define an Angry trait.

  trait Angry { self: Person =>
    def growl = self.speak("I’m having a bad day!!")
  }

Note the annotation self: Person =>. This is called a self type. It does two things.

  1. It restricts the trait to being used with the Person class, or a sub-class of Person. For example, we can’t mix in the Angry trait with a Bird class to create an Angry Bird. Self types declare the dependency of the trait on another type, which means, in effect: "In order to use me, you have to be one of those". Another way of looking at it is that the Angry trait requires the Person class.
  2. It allows the trait to access the methods and properties of the required type through the self variable, which explicitly declares the type of the variable and allows it to be used.

The annotation essentially makes the named type (in this case Person) the this of the trait.

Some additional notes.

Using the variable name self for a self type is a common convention, but you can use any name for the variable, It’s also very common to see self types defined using this, for example.

  trait Whatever { this: Person => /* body of trait goes here */ }

The use of the variable (self or this) when accessing the self type, is optional, so the following will work just fine.

  trait Angry { self: Person =>
    def growl = speak("I’m having a bad day!!")
  }

While self types are most frequently used with traits, they can also be used with classes.

Self types allow a dependency to be expressed within a trait that, when satisfied, allows the trait to make use of the code defined in another type – without polluting the trait with the implementation details of the other type. That other type could be another trait, a class, or a structural type, which we will learn about in the Structural Types lecture of the Intermediate Scala course. The concrete class subclassed from the self-typed trait must also subclass the self type, of course.

Scala Self-Types by Gregor Heine provides more information on object-oriented modeling using self types.

Exercise - Self Types

Given the following class that models how people express their feelings.

  class Person(name: String) { def speak(feelings: String) = println(feelings) }
We also have a trait that provides a mode of expression if the Person instance is angry. This trait adds a method called growl to the Person when mixed in:
  trait Angry { self: Person =>
    def growl = self.speak("I’m having a bad day!!")
  }
Use the REPL or write a Scala console application to:
  1. Mix the Angry trait into the Person case class, and get an instance of the Person class to growl.
  2. Define a Happy trait and get the Person instance to laugh.

 .

Solution

These solutions are provided in courseNotes/src/main/scala/solutions/Emotions.scala.

  package solutions

  object Emotions extends App {

    class Person(val name: String) {
      def speak(feelings: String): Unit = println(feelings)
    }

    trait Angry { self: Person =>
      def growl = self.speak(s"$name is having a bad day")
    }

    trait Happy { self: Person =>
      def laugh = self.speak(s"$name is happy")
    }

    // Solution 1:
    (new Person("Fred Flintstone") with Angry).growl
    (new Person("Wilma Flintstone") with Angry with Happy).laugh

    // Solution 2:
    class AngryPerson(override val name: String) extends Person(name) with Angry
    class HappyPerson(override val name: String) extends Person(name) with Happy
    new AngryPerson("Bambam Rubble").growl
    new HappyPerson("Pebbles Flintstone").laugh
  }

You can run this by typing.

Scala REPL
$ scala> sbt "runMain solutions.Emotions"

Self Types vs Inheritance vs Composition

Self types add another tool in object-oriented modeling.

  1. Inheritance: The is a relationship. When you say B extends A, then B is an A
  2. Composition: The has a relationship. When you put a B inside an A by defining a val or a var, then A has B (or B is contained in, or is part of A)
  3. Requires: requires a relationship. When you put self: A => in type B then B requires A

Note that If trait A extends B, then mixing in A gives you precisely B plus whatever A adds or extends. In contrast, if trait A has a self-reference which is explicitly typed as B, then the ultimate parent class must also mix in B or a subtype of B (and mix it in first, which is important). In the first case, the precise type of B is crystallized at the point A extends it. In the second, the designer of the subtype gets to decide which version of B is used, at the point where the subtype is composed.

Self Types and Composition

The example code for this section can be found at: courseNotes/src/main/scala/Bicycle.scala

To compare self types and composition, let’s build a bicycle. We’ll also use some other things we’ve learned about object oriented programming with Scala. We are going to calculate gear ratios and speed, so our bike is composed of a ChainRing (the front gear), and a Wheel (the rear wheel).

Here’s what our Bike class looks like.

  /** A bike has a chainring and rear wheel components */
  case class Bike(chainRing: ChainRing, wheel: Wheel) {
    def gearRatio(gear: Int): Double = chainRing.frontTeeth.toDouble / wheel.gearTeeth(gear).toDouble

    def distancePerRevolution(gear: Int): Double = gearRatio(gear) * wheel.turn(1)

    /** @param rpm revolutions per minute that the cyclist pedals
      * @param gear gear number that is currently engaged */
    def rpmToKph(rpm: Int=90, gear: Int=1): Double = distancePerRevolution(gear) * rpm * 60.0 / 1000.0

    val numGears: Int = wheel.numGears
  }
  

In this case we use a has a relationship, where a Bike has a ChainRing and a Wheel. Now let’s define the class ChainRing and a single concrete subclass of a ChainRing with 50 teeth.

  /** Assume a single front chainring for simplicity */
  trait ChainRing {
    def frontTeeth: Int
  }

  class ChainRing50 extends ChainRing {
    val frontTeeth = 50
  }

  class ChainRing48 extends ChainRing {
    val frontTeeth = 48
  }
  

Next we define the Wheel class. Here we use self types and a requires relationship. A Wheel requires a gear Cassette and a Tire with a specific size. In the example below we also create two different concrete wheels, and TrainingWheel and a RacingWheel.

  /** A wheel requires a tire and a cassette */
  trait Wheel { self: Tire with Cassette =>
    /** @param numTurns a number of turns of the bicycle wheel as a real number
      * @return the total distance travelled for the given number of turns, in meters  */
    def turn(numTurns: Double): Double = self.tireSize * numTurns

    /** @param gear is the gear number, with the lowest number being the outside gear
      * @return the number of teeth of the given gear */
    def gearTeeth(gear: Int): Int =
      if (gear > rearTeeth.size) rearTeeth.last
      else if (gear < 1) rearTeeth.head
      else rearTeeth(gear - 1)
    def numGears = self.rearTeeth.size
  }

  class TrainingWheel extends Wheel with TrainingTire with TouringCassette

  class RacingWheel extends Wheel with RacingTire with RacingCassette
  

Now all that’s left is to define a Tire trait, and a Cassette trait that we can mix in to a Wheel to make different types of wheels. We need a collection of Int to hold the number of rear teeth on each gear of the cassette. Because we will be performing random accesses into this immutable collection, we use the Vector collection type instead of List or Array. The Immutable Collections lecture of the Intermediate Scala course explains Vector in more detail; that lecture is one of five lectures in that course dedicated to collections. Scala collections are parametric, and the Parametric Types lecture of the Intermediate Scala course discusses parametric types, also known as generic programming. For now, we’ll just recognize that the rearTeeth variable is an instance of a Vector collection of Int.

   trait Cassette {
    /** The number of cogs on each gear in the cassette */
    protected def rearTeeth: Vector[Int]
   }

  trait TouringCassette extends Cassette {
    protected val rearTeeth = Vector(14, 16, 18, 20, 22, 24, 26)
  }

  trait RacingCassette extends Cassette {
    protected val rearTeeth = Vector(11, 12, 13, 14, 15, 17, 19, 21, 23, 25)
  }

  trait Tire {
    /** Circumference is measured in meters */
    def tireSize: Double
  }

  trait RacingTire extends Tire {
    val tireSize = 2.096
  }

  trait TrainingTire extends Tire {
    val tireSize = 2.196
  }
  

Now let’s create some bicycles and try out our model.

  val racingBike = Bike(new ChainRing50, new RacingWheel)
  println(f"racingBike.distancePerRevolution(1) = ${racingBike.distancePerRevolution(1)}%2.1f meters")
  println(f"racingBike.rpmToKph(95, 1) = ${racingBike.rpmToKph(95, 1)}%2.1f KpH")

  val touringBike = Bike(new ChainRing48, new TrainingWheel)
  println(f"touringBike.distancePerRevolution(5) = ${touringBike.distancePerRevolution(5)}%2.1f meters")
  println(f"touringBike.rpmToKph(95, 5) = ${touringBike.rpmToKph(95, 5)}%2.1f KpH")

  // Calculate speeds for each gear on the touring bike
  println("When riding a Touring Bike at 95 RPM, the speeds for each gear are:")
  1 to touringBike.numGears foreach { gear =>
    println(f"Gear $gear: Speed is ${touringBike.rpmToKph(95, gear)}%2.1f KpH")
  }

You can run this by typing:

Scala REPL
$ scala> sbt "runMain Bicycle"

Output is.

  racingBike.distancePerRevolution(1) = 9.5 meters
  racingBike.rpmToKph(95, 1) = 54.3 KpH
  touringBike.distancePerRevolution(5) = 4.8 meters
  touringBike.rpmToKph(95, 5) = 27.3 KpH
  When riding a Touring Bike at 95 RPM, the speeds for each gear are:
  Gear 1: Speed is 42.9 KpH
  Gear 2: Speed is 37.6 KpH
  Gear 3: Speed is 33.4 KpH
  Gear 4: Speed is 30.0 KpH
  Gear 5: Speed is 27.3 KpH
  Gear 6: Speed is 25.0 KpH
  Gear 7: Speed is 23.1 KpH
  

Formatted String Interpolation

Back in the Learning Scala Using the REPL lecture we introduced Scala string interpolations using the s string interpolation. You will note in the above example we’ve used the formatted string interpolation, or f interpolation, to format the output.

To recap, a string of the form s"Some text ${myExpression}" will have the value of the expression myExpression converted to a string and inserted in place of ${myExpression}. Each variable will automatically be converted to a string by calling its toString method, and expressions within { curly braces } are evaluated and the result’s toString method is called. The curly braces are optional for simple variable references, but are required when referencing properties or classes of objects.

Formatted string interpolation are similar to simple string interpolation, except it adds printf-style formatting and uses the f prefix. Scala uses Java’s Formatter class for f string interpolation formatting. The java.util.Formatter documentation describes the available formatting directives. The references to expressions use the same s"Some text ${myExpression}" format as before, however the printf formatting directives are appended to the end of the expression with no spaces.

Here are some examples of formatting directives.

  object FormattedStringSamples extends App {
    val int = 123
    val double = 123.4567
    val string = "Hello"
    println(f"Signed Integer right-justified at least 6 wide                                                     |$int%6d|")
    println(f"Signed Integer left-justified at least 6 wide                                                      |$int%-6d|")
    println(f"Integer right-justified at least 6 wide, zero filled                                               |$int%06d|")
    println(f"Integer right-justified at least 6 wide, leading +                                                 |$int%+6d|")
    println(f"Floating point right-justified at least 10 wide, 2 positions after the decimal                     |$double%10.2f|")
    println(f"Floating point left-justified at least 10 wide, 2 positions after the decimal                      |$double%-10.2f|")
    println(f"Floating point scientific notation right-justified at least 10 wide, 2 positions after the decimal |$double%10.2E|")
    println(f"Floating point scientific notation left-justified at least 10 wide, 2 positions after the decimal  |$double%-10.2E|")
    println(f"String minimum length 10, right-justified                                                          |$string%10s|")
    println(f"String minimum length 10, left-justified                                                           |$string%-10s|")
    println(f"Computation minimum length 10, right-justified                                                     |${int * double}%-10.2f|")
    println(f"Computation minimum length 10, left-justified                                                      |${int * double}%10.2f|")
  }
  

You can run this by typing.

Scala REPL
$ scala> sbt "runMain FormattedStringSamples"

Output is.

  Signed Integer right-justified at least 6 wide                                                     |   123|
  Signed Integer left-justified at least 6 wide                                                      |123   |
  Integer right-justified at least 6 wide, zero filled                                               |000123|
  Integer right-justified at least 6 wide, leading +                                                 |  +123|
  Floating point right-justified at least 10 wide, 2 positions after the decimal                     |    123.46|
  Floating point left-justified at least 10 wide, 2 positions after the decimal                      |123.46    |
  Floating point scientific notation right-justified at least 10 wide, 2 positions after the decimal |  1.23E+02|
  Floating point scientific notation left-justified at least 10 wide, 2 positions after the decimal  |1.23E+02  |
  String minimum length 10, right-justified                                                          |     Hello|
  String minimum length 10, left-justified                                                           |Hello     |
  Computation minimum length 10, right-justified                                                     |15185.17  |
  Computation minimum length 10, left-justified                                                      |  15185.17|

Self Types and Inheritance

The code in this section is provided in Traits2.scala.

To see the difference between a self type and subclassing a trait, let’s define a User trait, then use it twice, once with a Tweeter trait that subclasses User and once with a Tweeter2 trait that uses self typing to merely require User.

  trait User { def name: String }

  trait Tweeter extends User {
    def tweet(msg: String): Unit = println(s"$name: $msg")
  }

  trait Tweeter2 { self: User =>
    def tweet(msg: String): Unit = println(s"${self.name}: $msg")
    def tweet2(msg: String): Unit = println(s"$name: $msg")
  }

  class Blabber(val name: String) extends Tweeter
  class Blabber2(override val name: String) extends Tweeter2 with User
  

Some observations about this code.

  • The User trait defined the name property as a def, in accordance with the uniform access principle, which we will discuss in the Setters, Getters and the Uniform Access Principle lecture.
  • The Tweeter trait extends User, so a Tweeter is a User because a Tweeter is a User subclass. This means that the User implementation is completely visible within Tweeter.
  • Tweeter does not define User.name, so any concrete subclass of the Tweeter trait must fulfill the User contract by defining name.
  • The Tweeter2 trait does not extend User, so the Tweeter2 trait does not fulfill the User contract. In other words, a Tweeter2 is not a User, however it requires that the object which provides the concrete implementation of Tweeter2 must be a User. This means that if User is a pure trait or a Java interface, the implementation details of User are not exposed in the Tweeter2 trait.
  • The self type instance’s name is self, which is a common convention. Feel free to use any arbitrary name in your code if you prefer.
  • Blabber’s name parameter to the class constructor is also an immutable public property, so it fulfills the contract of the User trait, in that a name property must be defined.
  • Both the Blabber.name and the Blabber2.name parametersimplement User.name, and they do not need to be marked override. Whether you write the word override or not is a matter of personal style.

Now let’s try it out.

Scala REPL
scala> val blabber = new Blabber("Mr.
Itoktumuch")
blabber: Blabber = Blabber@57cd2786
scala> %}blabber.tweet("tweet tweet tweet") Mr. Itoktumuch: tweet tweet tweet
scala> %}val blabber2 = new Blabber2("Ms. Nufsaid") blabber2: Blabber2 = Blabber2@4da5181a
scala> %}blabber2.tweet2("Le tweet") Ms. Nufsaid:Le tweet
scala> %}blabber2.tweet("Une autre tweet") Ms. Nufsaid: Une autre tweet

You can run this code by typing.

Scala REPL
$ scala> sbt "runMain Tweeters"
Mr.
Itoktumuch: tweet tweet tweet
Ms.
Nufsaid: Le tweet
Ms.
Nufsaid: Une autre tweet

Using Scala’s Singleton as a Self Type

Scala has an little-known Singleton type, which can be used to enforce singletons. If you have an aversion to the singleton pattern, read no further. Although Singleton does not appear in the Scaladoc, it is mentioned in the Scala Language Specification, §3.2.1.

One way to use Singleton is to define a trait that can only be subclassed by singletons.

  trait Lonely { self: Singleton =>
    override def toString = "I am so shy!"
  }

  object OneAndOnly extends Lonely

  println(OneAndOnly.toString)

The compiler won’t let you define a class that extends the Lonely trait.

  class Oops extends Lonely

This is the error message.

  Error: illegal inheritance; self-type Oops does not conform to Lonely’s selftype Lonely with Singleton

You can run this code by typing.

Scala REPL
$ scala> sbt "runMain Wallflower"

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