Scala is a pretty good functional programming language, but its type class syntax is pretty ugly to use. Thankfully, there is a pattern you can use to simplify the use of your type classes, making them almost as easy to use as regular inheritance based OOP.

Type classes are a very handy concept first pioneered in Haskell, which allows polymorphism like inheritance, but without requiring the foresight of inheritance. By that I mean that type class relationships between types can be defined for any type after that type has been defined, rather than during that type’s definition.

Below, I have the definition of a type class called depth.

trait Depth[P[_]] { def depth(self: P[_]): Int }

This type class can be implemented for any type constructor like so:

object Depth { implicit object ListDepth extends Depth[List] { def depth(self: List[_]) = x.size } }

The code above basically tells Scala how Depth is implemented for the type constructor List. This is all well and good, but when we go to use our type class…

object Main extends App { List(1,2,3).depth //won't compile, list does not have depth defined def fn[T[_]: Depth](t: T[_]) = { t.depth //Also doesn't work implicitly[Depth[T]].depth(t) //yuck } }

things get very messy very quickly. Thankfully, with just a few tweaks, our typeclass can be as easy to use as regular polymorphism!

trait Depth[P[_]] { def depth(self: P[_]): Int class Ops[T](self: P[T]) { def depth = Depth.this.depth(self) } } object Depth { implicit object ListDepth extends Depth[List] { def depth(self: List[_]) = x.size } //This implicit converts anything that has an implementation of Depth into the Depth.Ops class implicit def implicits[T, P[T]](x: P[T])(implicit ev: Depth[P]) = new ev.Ops(x) }

As you can see, we added a new def to our Depth companion object called implicits and a class named Ops to our Depth trait. We simplify the depth function by making it apply itself to the depth object it belongs to. The implicit def called implicits converts any object X that has an implementation of the Depth type class into a Depth#Ops object. This makes our syntax in main easy to use and much nicer looking:

import Depth import Depth._ object Main extends App { List(1,2,3).depth //now this works just like if List inherited from Depth def fn[T[_]: Depth](t: T[_]) = { t.depth //So does this! } }

Type classes make certain code patterns much easier to deal with. For example, earlier today I had two different datastructures with similar methods I wanted to benchmark against each other. Problem is, they shared no inheritance, so under normal polymorphism I would have to either implement some kind of shared parentage or implement multiple near copies of the same benchmarking code. With typeclasses, there was a much easier third option. Implement a typeclass with the methods signatures I was going to use, and make both types implement that typeclass. Then I just needed to pass in a datastructure to the benchmark function and it would do the rest. Code gets reused and everyone is happy.

What are the downsides to the pattern above and type classes in general? They are:

Type classes are terrible for documentation, since any and all implementations of the type class can be defined anywhere in your code.

Method signature collisions between type classes implemented by a type can cause weird “method not defined” compiler errors.

Generic syntax along with implicits definitions can be finicky, and sometimes it takes work to figure out why your implicit is not triggering.

The implicit def that enhances the typeclass implementations must have a unique name. Thanks to type erasure, Scala can’t differentiate these methods solely on type signature. Collisions in the names of these methods will generate “method not defined” compiler errors.

All in all, typeclasses are a very nice and powerful tool for polymorphism in Scala!

edit: As Nullable has brought up, this can be done with implicit classes and view bounds in a more succint way:

trait Depth[T] extends Any { def depth: Int } object Depth { implicit class ListDepth[T](val self: List[T]) extends AnyVal with Depth[List[T]] { def depth = self.size } } object Main extends App { List(1,2,3).depth // 3 def fn[T <% Depth[T]](t: T) = t.depth //compiles }

However, Depth is no longer useable as a type class, meaning implicitly goes out the window.

—-

This post was inspired by Scala – The Flying Sandwich Parts by eed3si9n