Wednesday, January 19, 2011

Introduction to type classes in Scala in 5 minutes

The aim of this short blog post is to give you enough information about type classes (in Scala) that you won’t twitch every time someone mentions the subject on twitter. It will focus on one application of type classes: An alternative to ad-hoc polymorphism. Once you have more time I’d suggest reading the paper Type Classes as Objects and Implicits to get a better understand of the subject.

The above article states that type classes have two roles

  1. Define a set of requirements for the type parameters used by generic algorithms
  2. Propagate constraints automatically making generic algorithms convenient and practical to use

The first is achieved by defining a trait and the second by providing an implicit object which extends that trait for a given type and an implicit parameter to the generic algorithm.

Now to make it a bit less abstract lets use an example. Assume you want to write a method named ‘greedy’ which picks the largest of whichever two instances of the same class you pass to the method. For this we need to have some idea of the ordering of the instances. Instead of forcing the instances to extend some type you can accomplish it with type classes.

case class MyModel(val data: Int) 

trait Ord[T] { 
  def compare (x: T, y: T): Boolean
}

implicit object ordMyModel extends Ord[MyModel] { 
  def compare (m1: MyModel, m2: MyModel) = m1.data <= m2.data
}
 
def greedy[T](m1: T,m2: T)(implicit ordM: Ord[T]) = 
  if (ordM.compare (m1,m2)) m2 else m1

With the above code you can invoke the following in the REPL

val m1 = new MyModel(3) 
val m2 = new MyModel(5)
greedy(m1,m2)

The nice thing about this is that if you want to be able to use the method greedy with another type you simply have to implement and implicit object which extends the Ord[T] trait. This keeps the inheritance chain of your classes clean.

5 comments:

  1. More importantly, you can invoke

    greedy(m1,m2)

    because ordMyModel is implicit. Which is probably what you meant to write ;-)

    ReplyDelete
  2. Thanks Bart, you are completely right ;) I've removed it form the blog post now.

    ReplyDelete
  3. The object doesn't have to be implicit, does it? Just the parameter.

    ReplyDelete
  4. great little summary - thanks!

    ReplyDelete
  5. Nice example. You can also write greedy with a so-called context bound:

    def greedy[T : Ord](m1: T, m2: T) =
    if (implicitly[Ord[T]].compare(m1, m2)) m2 else m1

    ReplyDelete