Scala Type Classes
Examples
Simple Type Class
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 Strings. 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.
Extending a Type Class
This example discusses extending the below type class.
trait Show[A] {
def show: String
}
To make a class you control (and is written in Scala) extend the type class, add an implicit to its companion object. Let us show how we can get the Person class from this example to extend Show:
class Person(val fullName: String) {
def this(firstName: String, lastName: String) = this(s"$firstName $lastName")
}
We can make this class extend Show by adding an implicit to Person's companion object:
object Person {
implicit val personShow: Show[Person] = new Show {
def show(p: Person): String = s"Person(${p.fullname})"
}
}
A companion object must be in the same file as the class, so you need both class Person and object Person in the same file.
To make a class you do not control, or is not written in Scala, extend the type class, add an implicit to the companion object of the type class, as shown in the Simple Type Class example.
If you control neither the class nor the type class, create an implicit as above anywhere, and import it. Using the log method on the Simple Type Class example:
object MyShow {
implicit val personShow: Show[Person] = new Show {
def show(p: Person): String = s"Person(${p.fullname})"
}
}
def logPeople(persons: Person*): Unit = {
import MyShow.personShow
persons foreach { p => log(p) }
}