Translate

viernes, 18 de febrero de 2022

El poder de las type class y los implícitos en Scala


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:

  1. definiendo instancias concretas como valores implícitos del tipo requerido;
  2. 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...


Sin embargo, este enfoque claramente no escala. Terminamos requiriendo dos valores implícitos para cada tipo A en nuestra aplicación: uno para A y otro para Opción[A].

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

      }

  }

Este método construye un JsonWriter para Option[A] basándose en un parámetro implícito para completar la funcionalidad específica de A. Cuando el compilador ve una expresión como esta:

  Json.toJson(Option("A string"))

busca un JsonWriter[Option[String]] implícito. Encuentra el método implícito para JsonWriter[Option[A]]:

  Json.toJson(Option("A string"))(optionWriter[String])

y busca recursivamente un JsonWriter[String] para usarlo como parámetro para optionWriter:

  Json.toJson(Option("A string"))(optionWriter(stringWriter))

De esta forma, la resolución implícita se convierte en una búsqueda a través del espacio de posibles combinaciones de definiciones implícitas, para encontrar una combinación que cree una instancia de clase de tipo del tipo general correcto.