Chapter 8: Type Classes and Ad-hoc Polymorphism

Ad-hoc Polymorphism

Ad-Hoc polymorphism is something similar to the method-overloading for functional programmers. In pure functional languages like Haskell, they have parametric polymorphism, in which we are creating a generic method which works with the finite number of types. But what if we require to creates a method for the finite number of types with different implementations. In that case, ad-hoc polymorphism comes into the picture.

In general polymorphism or we can say, polymorphism in OOP, our data and behavior are in the same place, but in ad-hoc polymorphism, we separate out our data and behavior. OOP polymorphism example as below:

// Polymorphism
trait Animal(name : String) {
	def behaviour(): String
}

class Dog(name: String) extends Animal(name) {
	override def behaviour() = ???
}

class Cat(name: String) extends Animal(name) {
	override def behaviour() = ???
}

As you can see in the, above example, state and behavior in the same place but in ad-hoc they are in sperate place. We will achieve ad-hoc polymorphism via type classes.

Type Classes

The concept of type classes is actually introduced in Haskell. This is used to implement ad-hoc polymorphism. In Scala there is no special feature called type class like Haskell, but as we know scala is a combination of functional and object-oriented so we can implement type classes via scala inbuilt features like type parameterization or generics.

For designing a Type Class, there 3 rules:

  1. Define pure abstract types with at least one generic parameter.

  2. Implement the abstract type and create an instance on the basis of your passed type parameter.

  3. Create a method, which is directly interacted to perform an operation on the basis of the passed type parameter.

According to these 3 rules, we can design our type classes by using scala inbuilt features like traits, classes, implicit and more. The implementation of type classes in scala as below:

Implementation

As per our above polymorphism example, we now redesign our classes on the basis of type class and achieve ad-hoc polymorphism.

So, as per our first rule is “Define pure abstract types with at least one generic parameter”, how can we define a pure abstract type in Scala? The answer is simple “Traits”. By using traits we can define a pure abstract type with require abstract methods with generics parameter as below:

class Dog(name : String)
class Cat(name: String)

trait AnimalBehaviors[T] {
	def behavior (animal: T)
}

Our second rule is: “Implement the abstract type and create an instance on the basis of your passed type parameter“. In Scala, we can implement traits via mixing with a class or create a directly anonymous class as below:

class DogBehaviour extends AnimalBehaviors[Dog] {
	def behavior (dog: Dog) = ???
}

class CatBehavior extends AnimalBehaviors[Cat] {
	def behavior (cat: Cat) = ???
}

implicit val dogBehaviour = new DogBehaviour
implicit val catBehaviour = new CatBehaviour

As per our second rule, we implement abstract type and create an instance but playing on a trick here, we create our objects as an implicit because the compiler will automatically detect require implementation on the basis of passed type, which we define in our third rule.

Now third and final rule: “Create a method, which is directly interacted to perform an operation on the basis of the passed type parameter”. That method is performed as an abstract API which is used by others like below:

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

behavior[Dog](Dog(“Jimmy”)) // call DogBehaviour implemnetation
behavior[Cat](Cat(“Sweety”)) // call CatBehavior implemnetation

As you can see, we create a method with a generic parameter and accept the implicit object of animalBehavior. In the implementation, we are just delegating to the actual implementation of animal behaviors. When we call behavior with dog object, the compiler automatically detects implicit implementation of DogBehaviour and pass it to the place of calling and same with a cat.

Now, what we are performing here? we are actually calling the same method with different implementations as per our requires type. This is something similar to method overloading where we have the same name but different parameters, In OOPS which we called compile-time polymorphism. But following these three rules, we are actually performing ad-hoc polymorphism. One interesting part of ad-hoc polymorphism is, we are performing different action without changing our actual code.

In the simple terms, we can say that ad-hoc polymorphism is actually design pattern and type classes are a mechanism for implementing ad-hoc polymorphism.

For more information, you can visit below blogs:

Last updated