El poder de las type class y los implícitos radica en la capacidad del compilador para combinar definiciones implícitas al buscar instancias candidatas.
Anteriormente insinuamos que todas las instancias de type class son valores implícitos. Esto fue una simplificación. De hecho, podemos definir instancias de dos maneras:
- definiendo instancias concretas como valores implícitos del tipo requerido;
- mediante la definición de métodos implícitos para construir instancias a partir de otras instancias de clases de tipo.
¿Por qué construiríamos instancias a partir de otras instancias? Como ejemplo motivacional, considere definir un JsonWriter para Option. necesitaríamos un JsonWriter[Option[A]] para cada A que nos interesa en nuestra aplicación. Podríamos intentar resolver el problema por fuerza bruta creando una biblioteca de valores implícitos:
implicit val optionIntWriter: JsonWriter[Option[Int]] =
???
implicit val optionPersonWriter: JsonWriter[Option[Person]] =
???
// and so on...
Afortunadamente, podemos abstraer el código para manejar la Opción[A] en un constructor común basado en la instancia de A:
• si la opción es Some(aValue), escriba aValue usando el escritor para A;
• si la opción es None, devuelve JsNull.
Aquí está el mismo código escrito como una definición implícita:
implicit def optionWriter[A] (implicit writer: JsonWriter[A]): JsonWriter[Option[A]] =
new JsonWriter[Option[A]] {
def write(option: Option[A]): Json =
option match {
case Some(aValue) => writer.write(aValue)
case None => JsNull
}
}
Json.toJson(Option("A string"))