Chapter 9: Type Constraints

In scala, with type parameters like [T] we pass any type, but sometimes we require to pass specified type of the data. In that case, type constraints come into the picture. Let’s take an below example:

trait Avengers
case class CaptainMarvel() extends Avengers
case class CaptainAmerica() extends Avengers
case class IronMan() extends Avengers
case class Hulk() extends Avengers

def saveTheDay[T](superHero : T, city: String) = {
	// fights with villains and save the day of the city
}

saveTheDay[IronMan](IronMan, “New York”) // compiles and run successfully
saveTheDay[Hulk](Hulk, “Virginia”) // compiles and run successfully

According to the example, we are calling a saveTheDay method with passing superhero object for saving the day of the city. But what if we are passing an object of supervillain for saving the day? Is it make sense? Let’s try:

case class Loki() 
saveTheDay[Loki](Loki, “New York”) // compiles and run successfully

In the above code, we are passing the object of Loki to save the day of New York city. Is it digestible to you? This is our first problem, there is no way to control the object of any type for calling the method saveTheDay because as we know, in JVM based languages type parametrization is used by the compiler and [T] meany any type, and after type erasure it deletes type information and replace with Object type, so we can pass any object to the method.

Note: This is never ever possible, Loki is going to the save the day of the city, but he can destroy the city without any doubt.

Let’s jump into the second problem, as we know, in Avengers all of them have one goal which is to stop the Thanos. So for that, we are adding one common behavior to Avengers trait which is called stopTheThanos as below:

trait Avengers {
	def stopTheThanos(avengers: Avengers) = ???
}

def endGame[T](avengers : T) = avengers.stopTheThanos(avengers)
// code compiles fails no such method.

As per the above code, we have a method called endGame which is parameterized type, as we know, we can pass any type to this method but for saving the day from Thanos we need to call method stopTheThanos, but the problem is [T] is replaced by Object type as we discussed in chapter 7 after type erasure, and stopTheThanos method is part of Avengers type, so, how can we call this method?

Above two problems are the most common scenarios which we are facing while developing something with the help of parameterizing types. For solving that problem, Scala type system provides us beautiful feature which is called Type Constraints. Type constraints separated into two categories as below:

  • Least Upper Bound

  • Greatest Lower Bound

The general idea of type constraints is to add restrictions in our parameterize type for the specific type, which helps to restrict the scope of passed parameter and we can access specific behaviors or features of a restricted type.

Least Upper Bound ( <: )

The first problem, which we faced in our above code, we are going to resolve with the help of Least Upper Bound type constraints.

def saveTheDay[T](superHero : T, city: String) = {
	// fights with villains and save the day of the city
}

We have method saveTheDay, and the problem with this method is, we can pass any type here. As we saw in our early example, we are able to pass Loki as a superhero to saving the city, which is completely wrong. So, with the help of Least Upper Bound, we are going to restrict our types within the passed method type argument as below:

def saveTheDay[T <: Avengers](superHero : T, city: String) = {
	// fights with villains and save the day of the city
}

In the above code, we are adding a special operator <: which is called Least Upper Bound or in the methematical term you can say that. < (less than) or : (equals to) to specific type. In our example, we have type hierarchy start from Avengers which is supertype of other types called, IronMan, Hulk and more. With the help of Least Upper Bound, we restricted only those types which are same or subtype of Avengers are allowed to pass as a superhero method parameters otherwise we are getting compile time error as below:

saveTheDay[IronMan](IronMan, “New York”) // compiles and run successfully
saveTheDay[Hulk](Hulk, “Virginia”) // compiles and run successfully

saveTheDay[Loki](Loki, “New York”) // compile fails

error: type arguments [Loki] do not conform to method saveTheDay's type parameter bounds [T <: Avengers]
       saveTheDay[Loki](Loki, "New York")

With the help of <: (Least Upper Bound), we successfully restrict our types and take advantage of compile-time safety for passing only defined types to this method. If we don’t have any type constraints than there is a possibility we will face this issues during runtime, which are really costly for resolve.

Now, jump to the second problem, where we want to use specific behavior of passed parameterize type within our method as below:

trait Avengers {
	def stopTheThanos(avengers: Avengers) = ???
}

def endGame[T](avengers : T) = ???

All Avengers have only one goal in end game which is to stop the Thanos and we know the endGame method is parameterized type, but we want to call a stopTheThanos method within the endGame method from passed avengers type object. So, for solving that problem, again <: (Least Upper Bound) comes to the picture as below:

def endGame[T <: Avengers](avengers : T) = avengers.stopTheThanos(avengers)
// code compiles successfully. 

How this possible? why is it work? because we know, type erasure removes all type information and replace [T] with the Object type.

But here, things are a bit different. In the above code, we added type constraints with parameterized type [T]. Which means, while compiler read this code and before converting it into bytecode, it checks constraints are present, that means, user can pass type which equals to or less than Avengers type only, that means, rather than placing Object instead of T, compiler will add Avengers type instead of T as a method parameter and because of that, we can access all methods and features of Avengers type from the passed object.

This is the concept of <: (Least Upper Bound) in Scala, and we have something similar in Java as well with the help of extends keyword with parameterized type <T>.

Greatest Lower Bound ( >: )

In the mathematical term, >: (Greatest Lower bound) means type is > (greater than) or : (equals) to defined restricted type constraints as below:

def wallOfSuperHerosTeam[T >: Avengers](heroes: T) = ???

With the help of >: (Greatest Lower bound) , we restricted our type parameter, that means to the wallOfSuperHerosTeam method, we can pass only teams of super heros not individuals or we can say that we can pass Avengers and its superclass, not its subclasses as below:

trait Heroes
class DcHeroes extends Heroes
class MarvelHeroes extends Heroes

class Avengers extends MarvelHeroes
class JusticeLeague extends DcHeroes

case class CaptainMarvel() extends Avengers
case class CaptainAmerica() extends Avengers

wallOfTeam[MarvelHeroes](new MarvelHeroes) // Compile and run successfully
wallOfTeam[Heroes](new Heroes{}) // Compile and run successfully
wallOfTean[CaptainMarvel](CaptainMarvel()) // Compile fails because CaptainMarvel is sub class.

In this example, we can pass Avengers and its super types only but not its subtype. In Java we have something similar to super keyword, with generics type <T>.

Let's take some more complex and confusing example for the lower bound, but before proceeding further remember one thing:

In most of the static-based languages like Java, Scala, Haskell and in others, a type system is used for compile-time checks, so, user programs can reduce their mistakes during run-time. While designing and using generics or types, always think like a compiler or we can say “Be The Compiler”.

Let’s moving to our next examples:

wallOfTeam[DcHeroes](new DcHeroes) // Compilation fails

There is no surprise in the above code compilation fails because Avengers superclass is MavelHeroes. DCHeroes and MavelHeroes are siblings. So, when compiler checks wallOfTeam[DcHeroes], and DcHeroes is not in the hierarchy, it fails compilation.

wallOfTeam[Heroes](new DcHeroes) // Compiles and run successfully.

Now the question is, why this code compiles and run successfully? The answer is simple but a bit confusing. We are calling wallOfTeam method with Heroes type and as per type hierarchy Heroes is a supertype of Avengers, so, compiler checks Heroes is valid type, and the method parameter object which we are passing is DcHeroes, which is actually a subtype of Heroes. According to JVM based languages, we can pass subtype to its supertype in method arguments. So, according to that, the compiler allows for compile this code.

wallOfTeam(new DcHeroes) // Compile and run successfully

What happens now? While compiler compiles the above line of code, type inference comes into the picture. Type inference interprets this code and converts it into this :

wallOfTeam[Heroes](new DcHeroes)

Same as the last example code. This is the reason this compiles and run successfully. Under the hood >: (Greatest Lower bound) like [T >: Avengers] it actually replaces [T] with Object/AnyRef type, this is the reason it allows to pass any super type here. But with >: (Greatest Lower bound), we can only use to call those methods which are defined in Object/AnyRef type. The compiler will not allow to call any specific method of a type. In Scala, >: (Greatest Lower bound) are mostly used with Variance for handling some specific scenarios, which we will discuss in our next chapter.

In the next chapter, we will see the how compiler thinks to solve variance, which always confused to scala developers.

Context-Bounds

In the last chapter, we design our type classes and perform ad-hoc polymorphism. For implementing type classes, we performed three steps and in the third step, we created a method, which is directly interacted to perform an operation on the basis of the passed abstract type instance as below:

def behavior[T](animal : T)(implicit val animalBehavior: AnimalBehaviors[T]) = {
	animalBehavior.behavior(animal)
}

In the above code, we need to define a method signature with implicit value. For designing Scala libraries, type classes are the most common pattern which is used by various libraries like Scalaz and Cats and you can see, with implicits, our method signature looks huge and boilerplate. So, for the ease perspective context-bound comes into the picture. With the help of context-bound we reduce our boilerplate code and makes our method compact like below:

def behavior[T : AnimalBehaviors](animal : T) = {
	implicitly[AnimalBehaviors].behavior(animal)
}

As you can see, we defined [T: AnimalBehaviors], this doesn’t mean mathematical equals ( : ) like in our previous (<: , >: )bounds examples, here ( : ) means that the method defining an implicit parameter AnimalBehaviors of type T as we defined in the first context bound example. With the help of implicitly[AnimalBehaviors], it will find the implicit object of AnimalBehaviors[T] and call its behavior method.

In the simplest way, you can think like that the method signature:

def behavior[T : AnimalBehaviors](animal : T) = {
	implicitly[AnimalBehaviors].behavior(animal)
}

It converts it into below code by compiler

def behavior[T](animal : T)(implicit val animalBehavior: AnimalBehaviors[T]) = {
	animalBehavior.behavior(animal)
}

These are all bounds we have in Scala type system. If I am missing something, please let me know, so we can add here.

Last updated