A type class is simply a trait
with one or more type parameters:
trait Show[A] {
def show(a: A): String
}
Instead of extending a type class, an implicit instance of the type class is provided for each supported type. Placing these implementations in the companion object of the type class allows implicit resolution to work without any special imports:
object Show {
implicit val intShow: Show[Int] = new Show {
def show(x: Int): String = x.toString
}
implicit val dateShow: Show[java.util.Date] = new Show {
def show(x: java.util.Date): String = x.getTime.toString
}
// ..etc
}
If you want to guarantee that a generic parameter passed to a function has an instance of a type class, use implicit parameters:
def log[A](a: A)(implicit showInstance: Show[A]): Unit = {
println(showInstance.show(a))
}
You can also use a context bound:
def log[A: Show](a: A): Unit = {
println(implicitly[Show[A]].show(a))
}
Call the above log
method like any other method. It will fail to compile if an implicit Show[A]
implementation can't be found for the A
you pass to log
log(10) // prints: "10"
log(new java.util.Date(1469491668401L) // prints: "1469491668401"
log(List(1,2,3)) // fails to compile with
// could not find implicit value for evidence parameter of type Show[List[Int]]
This example implements the Show
type class. This is a common type class used to convert arbitrary instances of arbitrary types into String
s. Even though every object has a toString
method, it's not always clear whether or not toString
is defined in a useful way. With use of the Show
type class, you can guarantee that anything passed to log
has a well-defined conversion to String
.