Coproductos en iota (o ‘de aquellos Weekdays, estos Writers’) – parte 2

En anteriores episodios de Scalera…

Recordemos que, en el anterior post, definimos nuestro subconjunto de días de la semana favorito como un co-producto:

case object Monday
case object Tuesday
case object Wednesday
case object GroundhogDay

import iota._
import iota.syntax.all._
import TList.::

type Weekday = Cop[Monday.type :: Tuesday.type :: Wednesday.type :: TNil]

Ahora imaginemos que queremos definir un serializador para nuestros días de la semana…

Writer[T]

Pongamos que nuestro serializador es algo sencillo del estilo:

trait Writer[T] {
  def write(t: T): String
}
object Writer {
  def apply[T](implicit w: Writer[T]): Writer[T] = w
}

Que tiene definidas instancias para nuestros días:

implicit val mondayW: Writer[Monday.type] =
  _ => "monday"

implicit val tuesdayW: Writer[Tuesday.type] =
  _ => "TUESDAY"

implicit val wednesdayW: Writer[Wednesday.type] =
  _ => "wed"

implicit val madeUpdayW: Writer[GroundhogDay.type] =
  _ => "nonsense!"

Nada fuera de lo normal hasta ahora: una type class con sus instancias.
La cuestión ahora es, ¿cómo podemos definir un serializador genérico para Weekday?

La parte contratante de la primera parte…

Sabemos que

Weekday = Monday U Tuesday U Wednesday

Por lo que si tenemos definidos Writer[Monday.type], Writer[Tuesday.type] y Writer[Wednesday.type], podríamos decir que el serializador de días de la semana será:

WeekdayWriter = Writer[Monday.type] U Writer[Tuesday.type] U Writer[Wednesday.type]

La cuestión es, ¿cómo mapeamos cada día de la semana con su serializador?
Con el complejo a la vez que simple snippet:

import TList.Op.Map
type WeekdayWriter = Cop[Map[Writer, Weekday#L]]

Hemos definido el serializador de días de semana como el coproducto resultante de mapear(Map) cada tipo T contenido en la TList de días de la semana que compone el co-producto(Weekday#L) al tipo Writer[T].

Es decir, que el compilador internamente ha hecho algo parecido (salvando obviamente las distancias cuando comparamos tipos con instancias) a:

val weekdays = List(Monday, Tuesday, Wednesday)
val writers = weekdays.map{
  case Monday    => Writer[Monday.type]
  case Tuesday   => Writer[Tuesday.type]
  case Wednesday => Writer[Wednesday.type]
}

A partir de ahora WeekdayWriter no puede ser de ningún otro tipo que no sea Writer[T] donde T está definido en Weekday

Sí, pero ahora tienes dos coproductos sin forma de establecer una relación entre ellos

…ok, cierto.

…será considerada como la parte contratante de la primera parte

Sabemos que queremos algo del estilo:

def weekdayWriter[D](weekday: D)(implicit stuff): WeekdayWriter = ???

¿Qué evidencias implícitas necesitamos?

  • D puede ser cualquier cosa, pero necesitamos que tenga un Writer[D](si no se puede serializar, no nos vale)
  • algo que nos diga que ese Writer[D] es uno de los incluídos en WeekdayWriter. Cómo vimos en el post anterior de esta serie, esa evidencia es Cop.Inject.

Por lo tanto el método nos quedaría como:

def weekdayWriter[D](
  weekday: D)(
  implicit w: Writer[D],
  ev: Cop.Inject[Writer[D], WeekdayWriter]): WeekdayWriter = {

  //So if we know that w is injectable into WeekdayWriter...
  w.inject[WeekdayWriter] //Let's inject it
}

Incluso podríamos definir un helper para hacerlo más bonito:

implicit class AnyWeekdayWriter[D](
  day: D)(
  implicit w: Writer[D],
  ev: Cop.Inject[Writer[D], WeekdayWriter]){

  def write: String =
    weekdayWriter(day).value match {
      case w: Writer[D@unchecked] => w.write(day)
    }
}

Si lo probamos notaremos que:

Monday.write //"monday"
Tuesday.write //"TUESDAY"
GroundhogDay.write //NOPE! It doesn't compile

…incluso aunque hay un Writer[GroundhogDay.type] implícito, al no haber una evidencia que permita inyectarlo en el co-producto de WeekdayWriter, no permite serializarlo.

¿Y qué me aporta?

Supongamos el modelo inicial:

sealed trait Weekday
case object Monday extends Weekday
case object Tuesday extends Weekday
case object Wednesday extends Weekday

implicit lazy val mondayW: Writer[Monday.type] = _ => "monday"
implicit lazy val tuesdayW: Writer[Tuesday.type] = _ => "TUES"
implicit lazy val wednesdayW: Writer[Wednesday.type] = _ => "wed"

¿Cómo definirías un serializador para Weekday? Lo lógico sería hacer algo del estilo:

implicit def weekdayWriter: Writer[Weekday] = {
  case Monday => mondayW.write(Monday)
  case Tuesday => tuesdayW.write(Tuesday)
  case Wednesday => wednesdayW.write(Wednesday)
}

Si resulta que decidiésemos añadir un nuevo día a la semana tendríamos que:

  • añadir el case object
  • hacer que extienda de Weekday
  • añadir el serializador concreto
  • modificar el serializador genérico

Mientras que con el enfoque que hemos utilizado durante estos dos posts:

  • añadir el case object
  • modificar la definición de Weekday
  • añadir el serializador concreto

…venga va, tampoco es que nos ahorremos muchos sitios donde tocar pero, ¿qué ocurriría si quisieramos realizar otra agrupación, por ejemplo, por días impares?

Con el enfoque del co-producto sería tan sencillo como añadir el nuevo co-producto que representa la unión de los tipos de los días impares y la función para obtener el Writer[T]:

type OddDay = Cop[Monday.type :: Wednesday.type :: TNil]

type OddDayWriter = Cop[Map[Writer, OddDay#L]]
def odddayWriter[D](
  oddday: D)(
  implicit w: Writer[D],
  ev: Cop.Inject[Writer[D], OddDayWriter]): OddDayWriter = {
  w.inject[OddDayWriter]
}

Mientras que en el mundo de la herencia, donde hay muerte y sufrimiento…

sealed trait Weekday
sealed trait OddDay
case object Monday extends Weekday with OddDay
case object Tuesday extends Weekday
case object Wednesday extends Weekday with OddDay

implicit def weekdayWriter: Writer[OddDay] = {
  case Monday => mondayW.write(Monday)
  case Wednesday => wednesdayW.write(Wednesday)
}

Aunque es material denso, esperamos haberos ayudado a entender el fascinante mundo de los co-productos

Nos vemos en el próximo post.
¡Agur de limón!

Anuncios

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión /  Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión /  Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión /  Cambiar )

Conectando a %s