Iota coproducts (or how to make weeks shorter) – part 1

You may (or may not) remember that we occasionally mentioned co-products when we spoke about algebraic data types. We defined them as the natural way of expressing type union(Wine = White U Red U Espumoso). In this post we’ll try to define new enumerations expressed as co-products, for example, weekdays….

Intro: product vs. co-product

Just to sum up and to better understand the example, imagine you have a triplet of certain type elements:

(Int, String, Double)

If the data that stands for a student may be expressed as a triplet of her age AND name AND average score, we could say that a student may be expressed as the product of these three types:

case class Student(
  age: Int,
  name: String,
  avg: Double)

(Surprisingly case classes extend from something called scala.Product, ehem …)

On the other hand, if we’re told that the result of some web request may return the number of error rows OR the value we wanted to retrieve OR the amount of seconds to wait until next request try, we would be talking about a co-product.

Intro: helpless co-products in Scala

In Scala, defining an enumeration as a co-product can be as easy as:

sealed trait Color
case object Blue extends Color
case object Red extends Color
//...

But, what happens when the co-product former types are not related between them (Int, Animal, …)?

Well, we could use Either type:

type Or[A, B] = Either[A, B]
val v1: Int Or String = Left(2)
val v2: Int Or String = Right("hi")

Easy, right? But, what happens now if we want to point that the value could be an Int or String or Boolean? Things become more complicated:

type Or[A, B] = Either[A, B]
val v1: Or[String, Or[Int, Boolean]] = Left("hi")
val v2: Or[String, Or[Int, Boolean]] = Right(Left(2))
val v2: Or[String, Or[Int, Boolean]] = Right(Right(true))

iota co-products

One of the alternatives for avoiding this headache is iota, a library developed by 47deg for defining co-products in an awesome way.
It both supports scalaz and cats. In this post, we’ll see how it’s applicable to cats (because reasons…)

library-dependant

In order to start compiling this, we’ll start by adding some cats-core and iota-core dependencies to our project:

scalacOptions += "-Ypartial-unification" // Needed if you're using Scala 2.11+
libraryDependencies ++= Seq(
  "org.typelevel" %% "cats-core" % "1.0.1",
  "io.frees" %% "iota-core" % "0.3.4"
)

Start point

Let’s suppose we have to define an enumeration.
One of the most common ways to achieve it in Scala, as we mentioned somewhere before, would be with:

sealed trait Weekday
case object Monday extends Weekday
case object Tuesday extends Weekday
case object Wednesday extends Weekday
case object Thursday extends Weekday
case object Friday extends Weekday
case object Saturday extends Weekday
case object Sunday extends Weekday

Nevertheless, let’s skip some extra days (for making it simpler) and let’s remove the inheritance relationship (for making it more interesting):

case object Monday
case object Tuesday
case object Wednesday
case object GroundhogDay //Just for messing things up...

For expressing now the type union ( Weekday = Monday U Tuesday U Wednesday) we define with iota the co-product this way:

// Let's import some iota core stuff (including syntax)
import iota._
import iota.syntax.all._

// ...and also import TList (which it's something really similar to a Shapeless HList)
// it keeps info about the contained element types.
import TList.::

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

And after these wonderful mayan glyphs, how do we use the Weekday type?
Because the following definitively doesn’t work:

val day: Weekday = Monday //NOPE! It doesn't fit

For being able to use it, we’ll have to check how to inject Monday.type into our co-product.
If your very first reaction to the previous sentence was ‘eing?’, take a look at the following section about injection and projection concepts. Otherwise, jump onto page 30 for killing the troll skip next section.

Inject & Project

Without giving a full-detail view about injective and projective modules, let’s see an easy example for better understanding these concepts.
Let’s imagine I have defined the weekdays co-product as a case class with all possible values as Options so only one of these can be fulfilled:

// Crazy stuff BTW,
// defining a co-product as a product of Option's
case class Weekday(
  monday: Option[Monday.type] = None,
  tuesday: Option[Tuesday.type] = None,
  wednesday: Option[Wednesday.type] = None){

  require({
    val defined =
      List(monday, tuesday, wednesday)
        .filter(_.isDefined).size
    defined <= 1
  }, "A Weekday can only be one of the options")

}
object Weekday {
  def inject(day: Monday.type): Weekday =
    Weekday(monday=Some(day))

  def inject(day: Tuesday.type): Weekday =
    Weekday(tuesday=Some(day))

  def inject(day: Wednesday.type): Weekday =
    Weekday(wednesday=Some(day))
}

With this definition we try to illustrate that Monday.type is not actually a Weekday sub-type, but there’s a function (inject) that makes us able to create a Weekday from a Monday.type: this function injects the value into the co-product.

The projection, on the other hand, stands for the reverse process.
Within the same example previously showed we can say that, from a Weekday we can project its ‘tuesday’ part. As this field might be empty, we return it as an Option value:

object Weekday {

  // ... all previously defined stuff goes here ...

  def projectM(wd: Weekday): Option[Monday.type] =
    wd.monday

  def projectT(wd: Weekday): Option[Tuesday.type] =
    wd.tuesday

  def projectW(wd: Weekday): Option[Wednesday.type] = 
    wd.wednesday

}

And how can you do this with iota?

Easy peasy! Just by using the Cop.Inject type class, which is used for establishing the relationship between one of the types that composes the co-product and the co-product itself.
In our case, for injecting a Monday into a Weekday, we need the implicit evidence that it’s injectable:

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

implicitly[Cop.Inject[Monday.type, Weekday]]

Thanks to this evidence, we’ll be able of doing:

val wd: Weekday = Monday.inject[Weekday]

and also of retrieving the Monday projection from any Weekday:

val wd: Weekday = Tuesday.inject[Weekday]
val monday: Option[Monday.type] =
  Cop.Inject[Monday.type, Weekday].prj(wd)
assert(monday.isEmpty, "It's actually a wednesday!")

At next post, we’ll see how to generate serializers for these co-products: pure gold…

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