A Simple type class
Schilling Cider House, Seattle, WA

A Simple type class

Type classes provide a way of achieving ad hoc polymorphism. We’ll look at what they are, why they’re useful, how they’re typically encoded in Scala. We’ll make our own Simple type class that has a single operation called simplify returning a String, and provide instances of it for Scala’s List and Int classes, along with our own class C. Finally, we’ll add convenient syntax in the Scalaz style to make it extremely easy to use the simplify operation. This might be the most hands-on, easy to understand explanation of type classes you’ve ever encountered!


A common way to express polymorphism in Scala is inheritance. This is brittle because the polymorphic relationship between supertype and subtype must be defined where the subtype itself is defined. For example, if you have an Animal trait, your Dog class must declare its relationship as a subtype of Animal as part of its definition. This is problematic when you do not own potential subtypes (i.e. they’re provided by a library). It also tightly couples the subtype to all of its implementations of its potential supertypes. A given subtype may already have all the methods needed to implement the behavior of a given supertype but doesn’t know or care about the supertype at definition time. Though less common, another problem is a subtype may be able to implement a supertype’s operations in multiple, distinct ways (e.g. multiplication and addition implementations of a Monoid over Int). With inheritance this is impossible.


Let’s start with one of the simplest type classes in Scalaz: Equal. Equal describes “a type safe alternative to universal equality” (if you’re wondering why this is useful or necessary, ask me in the comments). Here’s an example of providing an instance of Equal over our own class C that uses value equality to compare two instances.

class C(val name: String)

implicit val equalC: Equal[C] = new Equal[C] {
  override def equal(c1: C, c2: C) = c1.name == c2.name

Now let’s say we want a notEqual method that works on any A provided there is evidence of Equal[A]:

def notEqual[A: Equal](a1: A, a2: A): Boolean =
  !implicitly[Equal[A]].equal(a1, a2)

val c1 = new C("foo")
val c2 = new C("bar")

notEqual(c1, c2)
//=> true

Sidenote for those unfamiliar with context bounds: the context bound [A: Equal] is what ensures we have an implicit Equal[C] instance available when running notEqual(c1, c2). An equivalent and perhaps more clear implementation without context bounds would look like this:

def notEqual[A](a1: A, a2: A)(implicit e: Equal[A]) = !e.equal(a1, a2)
notEqual(c1, c2)
//=> true

However, there’s an even more concise way of writing this using context bounds and Scalaz’ /== operator for instances of Equal, or even its unicode alias:

def notEqual[A: Equal](a1: A, a2: A): Boolean = a1 /== a2
// or
def notEqual[A: Equal](a1: A, a2: A): Boolean = a1  a2

Since Scalaz already provides these operators, notEqual is purely didactic.

Another common typelcass in Scalaz is Show. This corresponds to Haskell’s Show type class, and is used to indicate a type that can be represented in some way as a string, e.g. for logging or printing in a REPL. Here’s an instance for our C class example from before.

implicit val showC: Show[C] = new Show[C] {
  override def show(c: C) = s"C[name=${c.name}]"

Scalaz provides syntax helpers that allow us to simply call .show on any type that provides evidence of Show. We’ll look at how that works in detail toward the end.

new C("qux").show
//=> res22: scalaz.Cord = C[name=qux]


Now that we’ve gotten a taste for using a few of Scalaz’ type classes, let’s build our own, along with some syntax helpers in the Scalaz style.

trait Simple[F] {
  def simplify(f: F): String

This is our type class definition. It defines a single operation simplify for a given F and returns a String. Before we provide instances, let’s define a method that expects a Seq of Simple instances and outputs them separated by newlines.

def manySimple[A](simples: Seq[A])(implicit s: Simple[A]): String =
  "Many simples:\n\t" + simples.map(s.simplify).mkString("\n\t")

We can use this to easily try out instances. Let’s start with an instance of C:

implicit def simpleC: Simple[C] = new Simple[C] {
  override def simplify(c: C) = s"Simplified: ${c.show}"

We can manually call this by implicitly obtaining a Simple[C] then calling simplify:

implicitly[Simple[C]].simplify(new C("hello"))

Or we can try out the manySimple method:

manySimple(Stream(new C("foo"), new C("bar"), new C("qux")))
//=> Many simples:
//=>        Simplified: C[name=foo]
//=>        Simplified: C[name=bar]
//=>        Simplified: C[name=qux]

Let’s try another one: Int.

implicit val simpleInt: Simple[Int] = new Simple[Int] {
  override def simplify(i: Int) = s"Simplified Int with value of $i"

This is getting easy, right?

//=> Simplified Int with value of 123

We can even provide an instance for List[A] but only if A itself has a Simple instance:

// Evidence for Simple[List[A]] given evidence of Simple[A]
implicit def simpleList[A: Simple]: Simple[List[A]] = new Simple[List[A]] {
 override def simplify(l: List[A]) = {
   val simplifyA = implicitly[Simple[A]].simplify _
   s"Simplified list:\n${l.map(simplifyA).mkString("\n")}"

Try it out:

implicitly[Simple[List[Int]]].simplify((1 to 5).toList)
//=> Simplified list:
//=> Simplified Int with value of 1
//=> Simplified Int with value of 2
//=> Simplified Int with value of 3
//=> Simplified Int with value of 4
//=> Simplified Int with value of 5

Hopefully you have a feel for how this works (ignoring how unuseful our Simple is IRL). Next, let’s follow Scalaz lead and provide some convenient syntax for working with our new type class; typing implicitly[Simple[_]] over and over again is starting to get old.


Wouldn’t it be nice if we could just call .simplify on objects which provide evidence of a Simple? Well, we can via some neat implicit tricks. Check it out:

final class SimpleOps[F](val self: F)(implicit val F: Simple[F]) {
  final def /^ = F.simplify(self)
  final def  = F.simplify(self)

trait ToSimpleOps {
  implicit def ToSimpleOps[F](v: F)(implicit F0: Simple[F]) =
    new SimpleOps[F](v)

object simple extends ToSimpleOps

trait SimpleSyntax[F] {
  def F: Simple[F]
  implicit def ToSimpleOps(v: F): SimpleOps[F] =
    new SimpleOps[F](v)(SimpleSyntax.this.F)

// New definition of our Simple typeclass that provides an instance of
// SimpleSyntax
trait Simple[F] { self =>
  def simplify(f: F): String
  val simpleSyntax = new SimpleSyntax[F] { def F = Simple.this }

Here we’ve provided two syntax operators as aliases to simplify (because no type class is legit without unicode operator aliases).

import simple._

//=> Simplified Int with value of 1

new C("I am C") 
//=> Simplified: C[name=I am C]

//=> Simplified Int with value of 1
//=> Simplified Int with value of 2
//=> Simplified Int with value of 3

// boring
new C("bar") /^
//=> Simplified: C[name=bar]


Type classes are a powerful method of adding support for a set of operations on an existing type in an ad hoc manner. Because Scala doesn’t encode type classes at the language level, there is a bit of boilerplate and implicit trickery to create your own type classes (compare this with Haskell, where classes are elegantly supported at the language level) and operator syntax.

View the full code listing for this post.

Further reading

Trevor Hartman

Written by on

Automate or die