Translate

jueves, 10 de febrero de 2022

Type class en Scala


Type class es un patrón de programación que se origina en Haskell. Nos permiten ampliar las bibliotecas existentes con nuevas funciones, sin utilizar la herencia tradicional y sin alterar el código fuente de la biblioteca original.

Type class es una especie de interfaz que define algún tipo de comportamiento. Si un tipo es miembro de una clase de tipos, significa que ese tipo soporta e implementa el comportamiento que define la clase de tipos. La gente que viene de lenguajes orientados a objetos es propensa a confundir las clases de tipos porque piensan que son como las clases en los lenguajes orientados a objetos. Bien, pues no lo son. Una aproximación más adecuada sería pensar que son como las interfaces de Java, o los protocolos de Objective-C, pero mejor.

Veamos un ejemplo: 

ghci> :t (==)

(==) :: (Eq a) => a -> a -> Bool

Interesante. Aquí vemos algo nuevo, el símbolo =>. Cualquier cosa antes del símbolo => es una restricción de clase. Podemos leer la declaración de tipo anterior como: la función de igualdad toma dos parámetros que son del mismo tipo y devuelve un Bool. El tipo de estos dos parámetros debe ser miembro de la clase Eq (esto es la restricción de clase).

El tipo de clase Eq proporciona una interfaz para las comparaciones de igualdad. Cualquier tipo que tenga sentido comparar dos valores de ese tipo por igualdad debe ser miembro de la clase Eq. Todos los tipos estándar de Haskell excepto el tipo IO (un tipo para manejar la entrada/salida) y las funciones forman parte de la clase Eq.

Hay tres componentes importantes para implementar este patrón en Scala. Type class en Scala se implementan usando valores y parámetros implícitos y, opcionalmente, usando clases implícitas. Las construcciones del lenguaje Scala corresponden a los componentes de los type class de la siguiente manera:

  • traits: type classes;
  • implicit values: instancia del type class;
  • implicit parameters: donde se usa el type class use
  • implicit classes: es opcional, y facilita el uso de type class
Veamos estos puntos en más detalle : 

traits : Una clase de tipo es una interfaz o API que representa alguna funcionalidad que queremos implementar. En Scala, una clase de tipo está representada por un rasgo o traits con al menos un parámetro de tipo. Por ejemplo, podemos representar el comportamiento genérico de "serializar a JSON" de la siguiente manera:

// Define a very simple JSON AST
sealed trait Json
  final case class JsObject(get: Map[String, Json]) extends Json
  final case class JsString(get: String) extends Json
  final case class JsNumber(get: Double) extends Json
  final case object JsNull extends Json

// The "serialize to JSON" behaviour is encoded in this trait
trait JsonWriter[A] {
  def write(value: A): Json
}

JsonWriter es nuestra clase de tipo en este ejemplo, con Json y sus subtipos proporcionando código de soporte. Cuando lleguemos a implementar instancias de JsonWriter, el parámetro de tipo A será el tipo concreto de datos que estamos escribiendo.

Las instancias de una clase de tipo proporcionan implementaciones de la clase de tipo para tipos específicos que nos interesan, que pueden incluir tipos de la biblioteca estándar de Scala y tipos de nuestro modelo de dominio.

En Scala, definimos instancias creando implementaciones concretas de la clase de tipo y etiquetándolas con la palabra clave implícita:

final case class Person(name: String, email: String)
  object JsonWriterInstances {
    implicit val stringWriter: JsonWriter[String] =
      new JsonWriter[String] {
        def write(value: String): Json =
          JsString(value)
      }

implicit val personWriter: JsonWriter[Person] =
  new JsonWriter[Person] {
    def write(value: Person): Json =
      JsObject(Map(
        "name" -> JsString(value.name),
        "email" -> JsString(value.email)
      ))
  }

//etc 
}

Puff me quedo relargo el post, seguimos con implicit parameters y implicit classes en el próximo post.