Scala Tutorials Part #8 - Traits


Traits

This is part 8 of the scala tutorial series. Check here for the full series.

: This article has been translated to chinese by ChanZong Huang, you can check it out here

Index

Introduction to traits

Traits are a new concept to scala just like case classes are. They complement to the already existing features in OOP.

A better way to understand traits is comparing it with java’s language features.

They are kind of similar to interfaces, but they can have implementations of methods, which works like abstract classes except that traits do not have constructors.

Best way to understand them is via examples. So let’s get our hands dirty.

A basic trait - Syntax explanation

Declaring a trait begins with the trait keyword and then the trait name, followed by the body/content of the trait.

trait Book {

  val id : Int
  val name : String
  val isbn : Long

}

Concept of abstract variables in java

If you are coming from a java background, then the above example might confuse you. In java, there is no such thing as abstract variable in java.

Only methods and classes can be abstract in java, variables cannot.

Abstract variable error in java

And since we do not have abstract variables, we also cannot override variables.

Field override error

The reason why scala is different in this aspect i.e why it has abstract variables has to do with two functional programming concepts. One is uniform access principle
which we saw earlier in classes and another is referential transparency.

As these concepts are complex they require additional posts of their own, we will explore them later.

Since scala strives to see methods and values as the same using uniform access principle i.e everything is a value type we have abstract variables as well.

Type annotations for abstract variables

Abstract variables should have their types annotated regardless of whether they are in traits or in abstract classes. I should have explained this concept when I was explaining scala classes but since traits also use them I thought I will explain it here.

If we do not mention the type, then we get an error as below.

Abstract variables without types

Remember that scala has local type inference, so we need to annotate the types explicitly.

For methods the scenario is slightly different. We can declare a method such as below.

trait Book {

  def noExplicitTypeAnnotation

}

It is a valid syntax and compiles without error.

There are subtle differences to note here. def noExplicitTypeAnnotation is the same as def noExplicitTypeAnnotation() : Unit. The type Unit is equivalent of void in java. It indicates the absence of an element in the literal void sense. This is not to be confused with other types that exist in scala such as Nothing and Null which I will cover later.

The Unit type makes no sense when applied to variables and hence the compiler does infer to that type as it does for methods.

We can however assign Unit as a type to a variable.

 
  val id : Unit = Unit

  println(id)

It would print () but it doesn’t make sense to annotate a variable with a Unit type.

Mixing abstract and concrete members

So far we have seen only abstract variables. In reality we would use a mixture of abstract variables,methods and/or concrete variables/methods.

Let’s take an example.

trait Book {

    val id : Int
    val name : String
    val isbn : Long
    val price : Double
    //Concrete variable
    val category = "Uncategorized"
  
   //Concrete implementation  
   def getTaxOnPrice : Double = {
     (price * 14)/100
   }

}

As we saw in the methods tutorial, if we omit the part after the = then it becomes an abstract method.

Notice that type inference works for concrete variables since it is known at compile time and type annotation is optional.

Java syntax differences

In java, we have interfaces and abstract classes and we extend abstract classes/classes and implement interfaces.

public class Child extends Root implements Interface1,Interface2 {
}

Scala does not have both interface and the implements keyword.

No implements error

Interface error

There are subtle differences in syntax and there is also a new keyword called with which we will explore below.

Extending traits

Similar to java, scala has the extends keyword which can be used for extending classes and traits.

Traits can be extended by other traits,abstract classes,concrete classes and case classes as well.

Traits extending traits

Since traits cannot be instantiated, it is not necessary that the abstract members have to implemented.

trait ScienceBook extends Book{
  
}

But if we need to implement any of the concrete members, we need the override modifier.

Trait override error

A correct version of the above would be.

trait ScienceBook extends Book{

  override val category: String = "Science Book"

}

Abstract classes extending traits

Abstract classes can also extend traits.

The same principles which apply to traits extending traits are also applicable here.

abstract class ScienceBook extends Book{

  override val category: String = "Science Book"

}

Classes extending traits

Since classes are concrete i.e instances can be created, the abstract members of the trait should be implemented.

Abstract members impl

A correctly implemented version of the class would be as follows.

class ScienceBook extends Book{

  override val id: Int = 1000
  override val name: String = "A Brief History of Time"
  override val isbn: Long = 9783499605550l
  override val price: Double = 7.43
  
}

We did not implement the getTaxOnPrice method and the variable category variable which is fine since they are concrete members. The type annotations are of course optional, they were present in my code example since Intellij auto-generated them.

If we need to change the logic we can of course override their implementation.

class ScienceBook extends Book{

  override val id: Int = 1000
  override val name: String = "A Brief History of Time"
  override val isbn: Long = 9783499605550l
  override val price: Double = 7.43

  override val category: String = "Science book"

  override def getTaxOnPrice : Double = {
    (price * 10)/100
  }
  
}

Since case class inheritance is a complex topic, I will be explaining that in a dedicated tutorial.

The with keyword

Since there is no concept of interfaces and implements keyword in scala, how would you go about extending a trait and then a class at the same time?

In java you would typically do it like

public class Root extends Ex1 implements Intef1,Intef2 {
}

Scala has a new keyword for it.

Let’s consider another abstract class called Product

abstract class Product {

  val prodID : Int
  val skuID : Int


}

Now since a book is a product we can combine the logic.

class ScienceBook extends Product with Book{

  override val id: Int = 1000
  override val name: String = "A Brief History of Time"
  override val isbn: Long = 9783499605550l
  override val price: Double = 7.43

  override val category: String = "Science book"

  override def getTaxOnPrice : Double = {
    (price * 10)/100
  }

 //Members of product abstract class
  override val prodID: Int = 20001504
  override val skuID: Int = 4574555
}

Mixin class composition

Now imagine that there are situations where we need not extend Product class and situations where we also need to extend.

This is impossible to solve with the OOP concepts that exist in java since we need to declare what the ScienceBook class extends beforehand.

Scala has something called mixins which can enable to do a mix of class compositions where we can choose to extend the Product features without modifying its original class hierarchy.

To demonstrate, we need to make a simple change to our abstract class Product and change it to a trait.

trait Product {

  val prodID : Int
  val skuID : Int
  
}

Next during instance declaration we can now extend the product trait with a different syntax as below.

  //Extension with mixin
  val scBook = new ScienceBook() with Product {
    override val prodID: Int = 1000
    override val skuID: Int = 2000
  }
  
  //Original class instance
  val scBookWithoutProduct = new ScienceBook()

Since we are creating an actual instance we need to override the abstract variables.

The original ScienceBook class however remains intact in its logic i.e it need not extend the Product trait. Now we have a the functionality of the Book,ScienceBook,Product classes and traits in a pretty neatly laid out way.

The scala doc article on understanding these mixins is also good.

Conclusion

Traits are more related to abstract classes than to interfaces. Main difference being traits do not have a constructor. Whenever you need to have a constructor for your OOP logic, then an abstract class will suit better, for all else traits are much better.

There are much more complex topics that traits open up, two in particular that I will cover in later tutorials are

1) Trait linearization - Since traits allow definitions and we can extend multiple traits how is the old problem of multiple inheritance handled?

2) Sealed traits - The sealed keyword says that classes in other files cannot extend the trait, but there is much more to this.

This brings an end to this article. I have covered the introductory and easier topics in this post and plan to cover advanced topics one at a time in later articles.

Stay tuned


Tagged Under


Scala


Search this website...




Keeping up with blogs...

I blog occasionally and mostly write about Software engineering and the likes. If you are interested in keeping up with new blog posts, you should follow me on twitter where I usually tweet when I publish them. You can also use the RSS feed , or even subscribe via email below.

Feedio Subscribe


Share & Like

If you like this post, then you can either share/discuss/vote up on the below sites.



Thoughts ...

Please feel free to share your comments below for discussion

Blog comments powered by Disqus