WTF is a Type Class?

Photo by Cris DiNoto

I have a hard time understanding about type class a couple of weeks back while reading about Scala with Cats.

After countless research on the internet and other tutorial explaining Type Classes, I also decided to share my take and the way I look at them.

By knowing that the author of Cats Library used this technique to implement a lot of the functionality, I think it is handy as it was a widely used technique in designing the FP application.

This technique does not only limited to FP, but OOP can also use this technique to create a more modular system.

Type class is an interface that defines some behavior.

It is a way to create any behavior from an existing class without modifying anything on that source code. Instead, it creates a type class to implement the new behavior.

Type class usually define in 3 things:

  • Trait Type Class
  • Instances of Type class: Defining the type class that we care about (concrete with implicit).
  • Interface objects or interface syntax

A simple example from Alvin Alexander blog: Let’s say you have these existing data types:

sealed trait Animal
final case class Dog(name:String) extends Animal
final case class Cat(name:String) extends Animal
final case class Bird(name:String) extends Animal

Assumed that one day you need to add new behavior to Dog because it can speak like humans, you want to add a new speak behavior, but without really changing the source code you already have, and also not changing the behavior for Cat and Bird

  1. Create a Trait that has a generic parameter:
    trait SpeakBehavior[A] {
      def speak(a:A):Unit
    }
    
  2. Create the instances of the type class that you want to enhance.
    object SpeakLikeHuman{
      implicit val dogSpeakLikeHuman: SpeakBehavior[Dog] = new SpeakBehavior[Dog] {
     def speak(a:Dog): Unit = {
       println(s"I'm a Dog, my name is ${dog.name})
     }
      }
    }
    
  3. Create an API for the consumers(caller) of this type class

The caller will be calling like this:

BehavesLikeHuman.speak(aDog) // interface object
aDog.speak // interface syntax (using implicit class)

Interface Object (more explicit):

object BehavesLikeHuman {
  def speak[A](a:A)(implicit instance: SpeakLikeHuman[A]) = instance.speak(a)
}

To call this type class:

import SpeakLikeHuman._ // import all the implicits
val dog = Dog("Rover")
BehavesLikeHuman.speak(dog)

Interface Syntax (implicit):

object BehavesLikeHumanSyntax {
  implicit class BehavesLikeHumanOps[A](a:A) {
    def speak(implicit instance:SpeakLikeHuman[A]): Unit = {
      instance.speak(a)
    }
  }
}

Calling this:

import SpeakLikeHuman._ // import the instances
import BehavesLikeHumanSyntax.BehavesLikeHumanOps

val dog = Dog("Rover")
dog.speak // because implicitly gets it from the implicit class BehavesLikeHumanOps

To me, calling Interface syntax is not as readable as calling Interface objects, especially when you are trying to read a large codebase.

Implicit in Scala can be a double-edged sword, and for someone who is not used to reading Scala code, it can be hard for the first time to read a large codebase with multiple implicit. However, if you are using interface syntax on your API, the caller can invoke a new behavior directly on the instance - it looks like you added a new behavior to the Dog instance without changing any of the source code.

3 Main Takeaways

  • Type Class is like inheritance, but in an FP way, you get to create new behavior on the model without modifying the existing source code.
  • There are 3 steps to create a Type Class - the Interface, the Type-class instance, and the API
  • Be aware of interface syntax, and implicits in Scala because sometimes it can be hard to debug if you used it too often in a large codebase.

Full Source Code on the example above is in here.

Like this Article?

Sign up for my newsletter to get this content weekly!

Subscribe

* indicates required

Related Posts

3 Useful Things About Either That You Want To Know

Either helps you construct better error handling

Let Me Teach You How To Implement Monad With Cats

Create your own Custom Monad with Cats Library

How to Construct an Immutable Queue

Constructing an Immutable queue with State Monad

How To Randomly Split Up An Elements From A Stream Of Data In Percentage

How to randomly split up element from a stream of data without knowing the total amount of it?

Demystify Builder Pattern in Scala

It is surprisingly simple to create a Builder Pattern in Scala