Chris James - Software Engineer and other things

What is a typeclass and why should you care

01 April 2014

Type systems are great because they protect you from lots of errors at compile time. However, sometimes it feels like it gets in your way and can make your code feel less flexible when you are trying to extend certain types that you may or may not have defined.

In dynamically typed languages this is less of a problem because of duck-typing. However, you get no safety here; if you pass in something that doesn’t quack you will get a runtime error.

Duck typing doesn't fix everything though. A human class might be able to quack with a "say" function; you would need to give your program some help in these instances

If you wish to write a generic function that operates on a variety of types, you may be tempted into writing an interface and ensuring all the types you care about implement them.

But this requires changing potentially lots of code and adding in a commonality that only you care about to solve a particular problem. Ask yourself, is this polymorphism truly useful for other people? Or are you polluting the namespace for your convenience (hint: the latter).

The GoF have a solution to this too, the adapter pattern. But every time someone wants to use your library they will have to take their type and wrap them up in an adapter. This feels clunky to me; and of course there is a better way.

Type classes to the rescue!

A type class T is an interface that defines a set of behaviours that a type Foo must implement in order to be of type T. Great, so what?

If you wish to have a polymorphic function, you can require evidence of a type class for the type which is being passed in. This can be any type because a developer can provide this evidence wherever they like; i.e not within the type itself.

This is a great strength of type classes because it addresses the flaws of the other approaches:


// Pretend this is your awesome library code that you want others to use freely
object MyAmazingSerialisationLib{

  trait Serialiser[A]{
    def toJson(x: A): String

  def writeJsonToDisk[A](item: A)(implicit serialiser: Serialiser[A]){
    def writeToDisk(x: String) {println(s"I saved [$x] to disk, honest!")}

object TypeClasses extends App {

  import MyAmazingSerialisationLib._

  case class Dog(name: String)

  case class ComputerMachine(id: Int, complexity: Int)

  implicit val dogSerialiser = new Serialiser[Dog] {
    def toJson(dog: Dog) = s"""{"name":"${}"}"""

  // compiles because we have evidence of serialiser

  // doesn't compile because we dont have a serialiser in scope
  writeJsonToDisk(ComputerMachine(2, 20)) 

Scala provides sugar to make it so your function's type can reflect it's needs, rather than having it as a parameter. You can replace the function from above with the code below and it will continue to work.

// [A: Serialiser] means the compiler expects an implicit Serialiser 
// needs to be available for A
def writeJsonToDisk[A: Serialiser](item: A){
  def writeToDisk(x:String){println(s"I saved [$x] to disk, honest!")}

  // Access the parameter with implicity

This syntax can provide more flexibility in terms of not having to pass implicit parameters to other sub-functions; your function just requires "evidence" of a type-class.

However, people in your team may be more familiar with an implicit parameter so be sure to explain things whenever you introduce exotic symbols into your code-base.


Other links

I originally posted this at spiking the solution