Iota coproducts (or ‘from those Weekdays, these Writers’) – part 2

Previously on Scalera…

Let’s remember that, on previous post, we defined our favourite weekday subset as a co-product:

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]

Now imagine we wanted to define a serializer for our weekdays…

Writer[T]

Assuming that our serializer is something like this:

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

And that it has several weekday defined instances:

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!"

…is not a big deal: a type class with its instances.
The question now is, how can we define a generic serializer for Weekday?

The first part of the party of the first part…

We know that

Weekday = Monday U Tuesday U Wednesday

So if we have defined Writer[Monday.type], Writer[Tuesday.type] and Writer[Wednesday.type], we could say that weekday serializer will be:

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

The question is, how can we map each weekday with its serializer? Well, the answer is with the following complex (and simple) snippet:

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

We’ve defined the weekdays serializer as the coproduct result of mapping(Map) each T type – contained inside the Weekday TList that composes the co-product(Weekday#L) – to the Writer[T] type.

In other words, internally the compiler has done something remotely similar (mind the gap between types and instances – compile time vs runtime) to:

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]
}

From now on, WeekdayWriter can’t be any other type appart from Writer[T] where T is defined inside Weekday.

Yes, but now you have two different unrelated co-products

Yep, true. Let’s establish the relationship.

…shall be known in this contract as the first part of the party of the first part…

We know that we want something similar to this:

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

Which implicit evidences do we need?

  • D can be anything, but we need it to have a Writer[D](if it can’t be serialized, it’s worthless)
  • something that tell us that Writer[D] is one of the included writers in WeekdayWriter. As we explained on the previous post, this evidence is Cop.Inject.

So the method would like this:

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
}

we could define a helper for making it even prettier though:

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)
    }
}

If we try it, we’ll notice that:

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

…even though there’s an implicit Writer[GroundhogDay.type], as there’s no evidence that proves that it’s injectable into the WeekdayWriter co-product, it’s not serializable as a Weekday.

So what does this provide?

Let’s suppose the initial model:

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"

How would you define a serializer for Weekday? Something logical would be:

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

If we decided to add a new day to the week we’d have to:

  • add the case object
  • make it extend from Weekday
  • add the concrete serializer
  • modify the generic serializer

Whereas the focus we’ve used so far would involve:

  • adding the case object
  • modifying Weekday definition
  • adding the concrete serializer

…okay, that’s not a big deal re-factoring but, what would it happen if we wanted to group those days in another way, for example, by odd days?

With the co-product focus would be as simple as defining the new co-product that stands for the type union of the odd days, as well as the function for getting the implicit 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]
}

And in the inheritance world, where there’s suffering and death…

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)
}

Although this is heavy metal, we hope we’ve helped you having a better understanding about the fascinating coproduct world.

See you next post.
Peace out!

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 )

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 )

Google+ photo

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

Conectando a %s