Back2Basics: Limit which classes a Trait can be mixed into!

Reading Time: 4 minutes

A Scala trait is just like a Java interface. A trait can be extended by different classes, just the way we do with Java interfaces and also a class can inherit multiple traits, called mixins. To know more about basics of the trait, refer to this blog.

But what if we want to restrict mixing of traits to a limited number of classes? Do we have a way out for the same?

The answer is YES. We can limit a trait so that it can be mixed into classes that show some specific behavior. There are three ways to do that. Let’s look at each of them one by one.

1. Limiting which class can use a Trait by Inheritance

You can limit a trait so that it will be mixed into classes only if they inherit a particular superclass.

The syntax for the same is :

trait [TraitName] extends [SuperThing]

where TraitName can only be mixed into classes that have SuperThing as their parent class, where SuperThing may be a class, or abstract class. In other words, Trait and the class that wants to mix that trait should have same parent class or trait.

Let’s understand this with the help of an example.

scala> class Animal
defined class Animal

scala> class Human
defined class Human

scala> trait Pet extends Animal
defined trait Pet

scala> class Dog extends Animal with Pet
defined class Dog

scala> class Male extends Human with Pet
:13: error: illegal inheritance; superclass Human
 is not a subclass of the superclass Animal
 of the mixin trait Pet
 class Male extends Human with Pet
                               ^

Now, let’s see what has happened here. We have two classes named ‘Animal’ and ‘Human’. And we have a trait ‘Pet’ that extends ‘Animal’ class, this adds a restriction on the classes which want to mix this trait. Only classes that extend ‘Animal’ class can mix ‘Pet’ trait in them.

Class ‘Dog’ was able to mix the trait ‘Pet’ because it has ‘Animal’ superclass, however, we can’t mix ‘Pet’ with ‘Male’ class, because, its superclass is ‘Human’ not ‘Animal’. Trying to do the same will give illegal inheritance error.

So, As long as a class and a trait share the same superclass the code will compile, but if the superclasses are different, the code will not compile.

2. Marking traits that can be used by Subclasses of a certain type

You can mark your trait so it can only be used by types that extend a given base type. To do that you have to write like this :

trait MyTrait {
   this: BaseType =>
   // more code here ...
}

where a trait named MyTrait can only be mixed into a class that is a subclass of a type named BaseType. This approach is referred to as a self-type. “Any concrete class that mixes in the trait must ensure that its type conforms to the trait’s self-type.”

Here is the example for the same :

scala> class Employee
defined class Employee

scala> trait StoreEmployee {
| this: Employee => 
| def doWork = "I am doing some work"
| }
defined trait StoreEmployee

scala> class Receptionist extends Employee with StoreEmployee
defined class Receptionist

scala> class Student extends StoreEmployee
:12: error: illegal inheritance;
self-type Student does not conform to StoreEmployee's selftype StoreEmployee with Employee
class Student extends StoreEmployee
                      ^

If you look at this carefully, you will observe that only class ‘Receptionist’ was able to mix ‘StoreEmployee’ trait, and when the same was tried with ‘Student’ class, compilation error occurred, as the later did not inherit ‘Employee’ class. Class ‘Student’ is not subclass of ‘Employee’, hence failed to conform to the StoreEmployee’s self-type.

You can also make sure that any type that wishes to extend a trait must extend multiple other types by doing something like this :

trait MyTrait {
   this: FirstType with SecondType with ThirdType =>
}

3. Ensuring a trait can only be added to a type that has a specific method

Using a variation of the self-type syntax, allows you to declare that any class that attempts to mix in the trait must implement the method you specify. The syntax for the same will be :

trait MyTrait {
 this: { def myMethod } =>
 // more code here ...
}

where trait MyTrait can be mixed to classes that implement myMethod. A trait can also require that a class have multiple methods. To require more than one method, just add the additional method signatures inside the block.

scala> trait Employee {
 | this : { def doSomeWork : String } => 
 | }
defined trait Employee

scala> class CorporateEmployee extends Employee {
 | def doSomeWork = "I am writing blog" 
 | }
defined class CorporateEmployee

scala> class Student extends Employee 
:12: error: illegal inheritance;
 self-type Student does not conform to Employee's selftype Employee with AnyRef{def doSomeWork: String}
 class Student extends Employee
                       ^

Here, class ‘CorporateEmployee’ implemented ‘doSomeWork’ method, however ‘Student’ class didn’t. Hence, the code didn’t compile.

This approach is known as structural type, because you’re limiting what classes the trait can be mixed into by stating that the class must have a certain structure, i.e., the methods you’ve defined.

So, these were the approaches to limit what classes a trait can be mixed into.

References

Stay tuned to learn more about Scala Traits. Happy Reading! 🙂


knoldus-advt-sticker


Written by 

I am a Software Consultant, having experience of more than 1 year. I am well versed with Object Oriented Programming Paradigms having good command of programming languages like Scala, Java & C++ and also skilled in building the microservices architecture based application using Lagom, Cassandra, Elasticsearch and many more. My hobbies include reading novels, writing blogs, drawing, listening to music.

2 thoughts on “Back2Basics: Limit which classes a Trait can be mixed into!4 min read

Comments are closed.

Discover more from Knoldus Blogs

Subscribe now to keep reading and get access to the full archive.

Continue reading