Taken from Essential Scala by Noel Welsh and Dave Gurnell
The type class pattern separates the implementation of functionality from the type the functionality is provided for.
In Scala, a type class is just a trait.
trait ExampleTypeClass[A] {
def doSomething(in: A): Foo
}
Full example:
trait HtmlWriter[A] {
def write(in: A): String
}
object PersonWriter extends HtmlWriter[Person] {
def write(person: Person) = s"<span>${person.name} <${person.email}></span>"
}
PersonWriter.write(Person("John", "john@example.com"))
To use a type class we:
- create implementations of that trait, called type class instances; and
- typically we mark the type class instances as implicit values.
- Normal scope where other identifiers are found (including local scope and imports).
- Companion objects of types involved in the method call with the implicit parameter.
For example:
sorted[B >: A](implicit ord: math.Ordering[B]): List[A]
The compiler will look in the following places for Ordering instances:
- the companion object of List;
- the companion object of Ordering; and
- the companion object of the type B, which is the type of elements in the list or any superclass.
Local scope takes precedence over instances found in companion objects (resutls in no ambiguity).
implicit class ExtraStringMethods(str: String) {
val vowels = Seq('a', 'e', 'i', 'o', 'u')
def numberOfVowels =
str.toList.filter(vowels contains _).length
}
"the quick brown fox".numberOfVowels
// res: Int = 5
When the compiler processes our call to numberOfVowels
, it interprets it as a type error because there is no such method in String
. Rather than give up, the compiler a empts to fix the error by searching for an implicit class
that provides the method and can be constructed from a String
. It finds ExtraStringMethods
. The compiler then inserts an invisible constructor call, and our code type checks correctly.
The compiler will not look to construct a chain of implicit classes to access the desired method.
trait Equal[A] {
def equal(v1: A, v2: A): Boolean
}
object Equal {
// Type Class Interface Pattern
def apply[A](implicit instance: Equal[A]): Equal[A] = instance
implicit class ToEqual[A](in: A) {
def ===(other: A)(implicit equal: Equal[A]): Boolean =
equal.equal(in, other)
}
}
implicit val caseInsensitiveEquals = new Equal[String] { def equal(s1: String, s2: String) =
s1.toLowerCase == s2.toLowerCase
}
import Equal._
"foo".===("FOO")
def pageTemplate[A](body: A)(implicit writer: HtmlWriter[A]): String = {
val renderedBody = body.toHtml
s"<html><head>...</head><body>${renderedBody}</body></html>"
}
can be written as:
def pageTemplate[A : HtmlWriter](body: A): String = {
val renderedBody = body.toHtml
s"<html><head>...</head><body>${renderedBody}</body></html>"
}