Coproductos en iota (o cómo hacer las semanas más cortas) – parte 1

Quizás recordaréis (o puede que no) que nombramos hace tiempo de pasada los co-productos cuando hablábamos de tipos algebraicos de datos. Los definíamos entonces como la manera natural de expresar unión de tipos (Wine = White U Red U Espumoso). En este post trataremos de definir enumerados expresados como co-productos, como por ejemplo, los días de la semana…

Intro: producto vs. co-producto

Por hacer algo de memoria y entender mejor el ejemplo, imaginad que tenéis una terna de elementos de ciertos tipos:

(Int, String, Double)

Si los datos que representan a un estudiante se pueden expresar como una terna de edad Y nombre Y nota media, podemos decir que un estudiante se expresa como el producto de estos tres tipos:

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

(Curioso además que las case classes extiendan de algo llamado scala.Product, ehem …)

Si por el contrario nos dicen que el resultado de una llamada web nos puede devolver el número de filas erróneas O el valor que queríamos recuperar O el número de segundos a esperar hasta nueva petición, estamos hablando de un co-producto.

Intro: Co-productos en Scala sin ruedines

En Scala, definir un enumerado como un co-producto puede ser tan sencillo como definir:

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

Pero, ¿qué ocurre cuando los tipos que forman el co-producto no tienen relación entre sí (Int, Animal, …)?

Bueno, podríamos usar el tipo Either:

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

Fácil, ¿no? Pero, ¿qué ocurre ahora si queremos indicar que el valor puede ser Int o String o Boolean? La cosa se complica un poco más:

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

Una de las alternativas para evitar este dolor de cabeza es iota, una librería desarrollada por 47deg para definir co-productos de una manera ‘awesómica’.
Tiene soporte tanto para scalaz como para cats. En este post, veremos como aplica a cats (because reasons…)

libro-dependiente

Para que esto empiece a compilar, empezaremos añadiendo a nuestro proyecto las dependencias de cats-core e iota-core:

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

Punto de partida

Supongamos que tenemos que definir un enumerado.
Una de las formas más comunes de hacerlo en Scala, cómo decíamos algún párrafo más arriba, sería con:

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

No obstante, quitemos algunos días de más (para hacerlo más sencillo) y eliminemos la relación de herencia que los relaciona (para hacerlo más interesante):

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

Para expresar ahora la unión de tipos ( Weekday = Monday U Tuesday U Wednesday) con iota definimos el co-producto como sigue:

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

Y después de estos bonitos glifos mayas, ¿cómo usamos el tipo Weekday?
Porque esto definitivamente no funciona:

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

Para poder usarlo, tendremos que ver cómo inyectar el tipo Monday.type en nuestro co-producto.
Si tu primera impresión a la frase anterior ha sido ‘eing?’, dale una ojeada a la siguiente sección sobre los conceptos de inyección y proyección. En caso contrario, salta a la página 30 para matar al troll puedes pasar a la siguiente.

Inject & Project

Sin entrar en mucho detalle sobre módulos inyectivos y proyectivos, veamos un sencillo ejemplo para entender los conceptos.
Imaginemos que yo hubiera definido el co-producto de los días de la semana como una case class con todos los posibles valores representados por un Option, de manera que solo uno de ellos puede estar relleno:

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

Con esta definición pretendemos ilustrar que Monday.type realmente no es un subtipo de Weekday, pero existe una función (inject) que nos permite crear un Weekday a partir de un Monday.type: esta función inyecta el valor en el co-producto.

La proyección, por otra parte, representa el proceso inverso.
En el mismo ejemplo de antes, podemos decir que, a partir de un Weekday, podemos proyectar su parte ‘tuesday’. Como podría estar vacío para ese campo, devolvemos el Option (que ya es por definición):

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

}

Y esto en iota, ¿cómo se hace?

Pues easy peasy, utilizando la type class Cop.Inject, que se utiliza para establecer la relación entre el tipo que compone el co-producto y el co-producto en sí.
En nuestro caso, para poder inyectar un Monday en un Weekday, necesitamos la evidencia implícita de que es inyectable :

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

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

Mediante dicha evidencia, seremos capaces de hacer:

val wd: Weekday = Monday.inject[Weekday]

Y también de obtener la proyección de Monday sobre cualquier 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!")

En la próxima entrega de la serie, veremos cómo generar serializadores para estos co-productos: canela fina…

¡Agur de limón!

Anuncios

Scalera tip: Uncle SAM

Today’s Scalera tip (and it’s been a while since last published tip) is about one of the Scala 2.12 version features that we fancy the most regarding the possibility of avoiding too much boilerplate.

Uncle SAM

Let’s suppose we’ve defined a type class that gathers certain amount of common implemented functionality like:

trait Comparisson[T]{

  def compare(t1: T, t2: T): Int

  def greaterThan(t1: T, t2: T): Boolean =
    compare(t1, t2) > 0

  def greaterEqualThan(t1: T, t2: T): Boolean =
    compare(t1, t2) >= 0

  def equalTo(t1: T, t2: T): Boolean =
    compare(t1, t2) == 0

  def lowerThan(t1: T, t2: T): Boolean =
    compare(t1, t2) < 0

  def lowerEqualThan(t1: T, t2: T): Boolean =
    compare(t1, t2) <= 0

  
}

…in that case the remaining job would consist on defining the compare method.
For creating the different instances of Comparisson we could either create implicit vals or implicit objects (nothing new about type classes so far):

object Comparisson {

  implicit val intComparisson: Comparisson[Int] =
    new Comparisson[Int]{
      def compare(t1: Int, t2: Int): Int = t1 - t2
    }

  implicit val booleanComparisson: Comparisson[Boolean] =
    new Comparisson[Boolean] {
      def compare(t1: Boolean, t2: Boolean): Int = {
        List(t1, t2)
          .map(b => if (b) 1 else 0)
          .reduce(_ - _)
      }
    }

}

Defining anonymous Comparisson instances (or extending from that trait in case of objects) was the only way of defining these instances so far.

With Scala’s 2.12 version a new concept known as SAM (Single Abstract Method) comes up. It basically allows defining an anonymous instance by providing a function equivalent to the only abstract method of the trait/abstract class.

When applying to the proposed example:

object Comparisson {

  implicit val intComparisson: Comparisson[Int] =
    (t1: Int, t2: Int) => t1 - t2

  implicit val booleanComparisson: Comparisson[Boolean] =
    (t1: Boolean, t2: Boolean) =>
      List(t1, t2)
        .map(b => if (b) 1 else 0)
        .reduce(_ - _)

}

Cute, right? Just as a reminder, do have in mind that if we don’t explicitly type the expression, the compiler won’t understand that it has to do its magic and it will assume that what we’ve just implicitly defined is a mere function:

object Comparisson {
  
  implicit val intComparisson =
    (t1: Int, t2: Int) => t1 - t2

  //  The previous inferred type will be (Int, Int) => Int

}

And we’re not telling this because it already happened to us…

Peace out!

Scalera tip: El tío SAM

En el tip de Scalera de hoy (y ha llovido ya un tanto desde el último que publicamos) hablaremos sobre una de las features de Scala 2.12 que nos ha parecido bastante interesante de cara a evitar escribir boilerplate

El tio SAM

Supongamos que hemos definido una type class que tiene bastante funcionalidad común del tipo:

trait Comparisson[T]{

  def compare(t1: T, t2: T): Int

  def greaterThan(t1: T, t2: T): Boolean =
    compare(t1, t2) > 0

  def greaterEqualThan(t1: T, t2: T): Boolean =
    compare(t1, t2) >= 0

  def equalTo(t1: T, t2: T): Boolean =
    compare(t1, t2) == 0

  def lowerThan(t1: T, t2: T): Boolean =
    compare(t1, t2) < 0

  def lowerEqualThan(t1: T, t2: T): Boolean =
    compare(t1, t2) <= 0

  
}

…en cuyo caso solo nos restaría definir el método compare.
Para crear las distintas instancias de Comparisson bien podemos crear implicit vals o implicit objects (hasta aquí nada nuevo sobre type classes en Scala):

object Comparission {

  implicit val intComparisson: Comparisson[Int] =
    new Comparisson[Int]{
      def compare(t1: Int, t2: Int): Int = t1 - t2
    }

  implicit val booleanComparisson: Comparisson[Boolean] =
    new Comparisson[Boolean] {
      def compare(t1: Boolean, t2: Boolean): Int = {
        List(t1, t2)
          .map(b => if (b) 1 else 0)
          .reduce(_ - _)
      }
    }

}

Definir instancias anónimas de Comparisson (o extender de dicho trait para el caso de objects) era la única forma de definir estas instancias hasta el momento.

Con la versión 2.12 de Scala surge el concepto de SAM (Single Abstract Method) que básicamente permite definir una instancia anónima aportando una función de orden superior equivalente al único método abstracto en el trait/abstract class.

Si aplicamos al caso anterior quedaría algo como:

object Comparisson {

  implicit val intComparisson: Comparisson[Int] =
    (t1: Int, t2: Int) => t1 - t2

  implicit val booleanComparisson: Comparisson[Boolean] =
    (t1: Boolean, t2: Boolean) =>
      List(t1, t2)
        .map(b => if (b) 1 else 0)
        .reduce(_ - _)

}

Cuqui, ¿no? Simplemente como recordatorio, tened en cuenta que si no anotamos el tipo de manera específica, el compilador no entiende que tiene que hacer su magia y asumira que lo que hemos definido de manera implícita es una función:

object Comparisson {
  
  implicit val intComparisson =
    (t1: Int, t2: Int) => t1 - t2

  //  The previous inferred type will be (Int, Int) => Int

}

Y no es que lo recordemos porque nos haya pasado a nosotros….

¡Agur de limón!

Abstract alge… what? Functors and cucumbers

Category mania! Are we paid for speaking about it? I’m afraid we don’t. Would we like to? Very likely. In previous posts we spoke about monoids and monads, but now it’s functor’s time. Functors are everywhere and we can prove it. Before reviewing its different kinds, let’s focus on key concepts.

What’s a morphism?

It’s just a transformation between two structures, a change between spaces, a mutation.
In Scala? A simple function:

val f: A => B

What’s a functor?

Quick and simple answer: the behavior that defines, for a certain F[A] type constructor, the map method.

trait Functor[F[_]]{
  def map[A, B](fA: F[A])(f: A => B): F[B]
}

There are functors for List, Option, Future, Try, …

object ListFunctor extends Functor[List]{
  def map[A, B](fA: List[A])(f: A => B): List[B] =
    fA match {
      case Nil => Nil
      case (head :: rest) =>
        f(head) +: this.map(rest)(f)
    }
}
object OptionFunctor extends Functor[Option]{
  def map[A, B](fA: Option[A])(f: A => B): Option[B] =
    fA match {
      case None => None
      case Option(a) => Option(f(a))
    }
}
//...

Actually, apart from the famous map method, they should fit into a couple of properties:

  • Identity morphism: F[Id[A]] = Id[F[A]]. For example, being identity the identity function defined in Scala, Option(identity(1)) == identity(Option(1))
  • Morphism composition: If we have two morphisms f: A => B and g: B => C, it must be checked that F[f o g] = F[f] o F[g]. It’s not as complicated as it seems:
    val f: Int => String = _.toString
    val g: String => Boolean = _.length > 1
    val l: List[Int] = List(1,20,3)
    l.map(f).map(g) ==
      l.map(f andThen g) ==
      l.map(n => g(f(n)))
    //List(false, true, false)
    

Even though, we can describe functors (in the context of a F[_] production chain) as the instructions for transforming some given A input value into some B output value within the same F[_] production chain, by using a morphism (a transformation function) A => B.

Functor types

Ordinary functors are also known as co-variant functors (in order to differentiate itself from contravariant and invariant functors). Let’s se what makes the difference between these different kinds of functor:

Contravariant Functor

Formal definition says that a F[_] functor is contravariant if, instead of having the map method, it has a contramap method defined:

trait Contravariant[F[_]]{
  def contramap[A, B](fA: F[A])(f: B => A): F[B]
}

That’s it: if it exists a function B => A, the contramap method defines F[A] => F[B].

…ok, let’s stop being so hardcore. We’ll illustrate it with an example.

Imagine a type class that knows how to compare certain type elements:

type Comparison = Int
val Greater = 1
val Equal = 0
val Lower = -1

trait Comparator[T]{
  def compare(t1: T, t2: T): Comparison
}

Imagine as well, that I know how to compare integer numbers (and here comes the tricky tip):

if I know how to compare integers and I know how to convert ‘cucumbers’ into integer numbers, I do know how to compare ‘cucumbers’

object ComparatorF extends Contravariant[Comparator]{
  def contramap[A, B]
    (fA: Comparator[A])
    (f: B => A): Comparator[B] =
    new Comparator[B]{
      def compare(t1: B, t2: B): Comparison =
        fA.compare(f(t1), f(t2))
    }
}

And now, the so-expected example about how to generate a contravariant functor for cucumbers:

trait Cucumber
val intC: Comparator[Int] = ???
val cucumberToInt: Cucumber => Int = ???
val cucumberC: Comparator[Cucumber] =
  ComparatorF.contramap(intC)(cucumberToInt)

cucumberC.compare(new Cucumber{}, new Cucumber{})

…sublime…

Invariant functors

Invariant functors for F[_] are determined by the imap method:

trait Invariant[F[_]] {
  def imap[A, B](fA: F[A])(f: A => B)(g: B => A): F[B]
}

…once again the doubt is killing you: I know and I beg you for some time to explain properly. Let’s see some example.

Imagine a type class that knows how to store stuff in some database (MongoDB, for example):

case class ObjectId(hash: String)

trait DataStorage[T]{
  def store(t: T): ObjectId
  def get(id: ObjectId): T
}

If we forget about possible effects (exceptions, timeouts and so) for not messing up the example, we could define an invariant functor for DataStorage that allows storing any-kind elements:

object DataStorageF extends Invariant[DataStorage]{
  def invariant[A, B]
    (fA: DataStorage[A])
    (f: A => B)
    (g: B => A): DataStorage[B] = {

    new DataStorage[B]{
      def store(b: B): Option[ObjectId] =
        fA.store(g(b))
      def get(id: ObjectId): B =
        f(fA.get(id))
    }
  }
}

So…

If I know how to store integers and I know how to convert integers into cucumbers (and the other way round), I know how to store cucumbers!

val intDS: DataStorage[Int] = ???
val cucumberToInt: Cucumber => Int = ???
val intToCucumber: Int => Cucumber = ???
val cucumberDS: DataStorage[Cucumber] =
  DataStorageF
    .imap(intDS)(intToCucumber)(cucumberToInt)

val id = cucumberDS.store(new Cucumber{})
val originalCucumber = cucumberDS.get(id)

We couldn’t say this is all, but it may be a good introduction to the wonderful functor world. See you in the next poxt. ‘Like’ and share if you like storing cucumbers.
Peace out!

Sources:
[1] Advanced Scala with Cats – Noel Welsh & Dave Gurnell
[2] Variance and functors – Typelevel blog

Teoría de Cate-movidas: Functores y pepinos

Que manía con las categorías. ¿Nos pagan por hablar de ello? No. ¿Nos gustaría que lo hiciesen? Es muy probable. En otras ocasiones hablábamos de monoides y mónadas, pero esta vez le toca el turno a los functores. Los functores están por todas partes y no son los padres: podemos demostrarlo. Antes de detallar sus variantes, centrémonos en los conceptos clave.

¿Qué es un morfismo?

Una transformación entre dos espacios, un cambio, una mutación.
¿En Scala? Una función:

val f: A => B

¿Qué es un functor?

Respuesta corta y simple: el comportamiento que define, para un constructor de tipos F[A], el método ‘map’:

trait Functor[F[_]]{
  def map[A, B](fA: F[A])(f: A => B): F[B]
}

Existen functores para List, Option, Future, Try, …

object ListFunctor extends Functor[List]{
  def map[A, B](fA: List[A])(f: A => B): List[B] =
    fA match {
      case Nil => Nil
      case (head :: rest) =>
        f(head) +: this.map(rest)(f)
    }
}
object OptionFunctor extends Functor[Option]{
  def map[A, B](fA: Option[A])(f: A => B): Option[B] =
    fA match {
      case None => None
      case Option(a) => Option(f(a))
    }
}
//...

En realidad, a parte del famoso método map, deben cumplir un par de propiedades más:

  • Morfismo identidad: F[Id[A]] = Id[F[A]]. Por poner un ejemplo, siendo identity la función identidad definida en Scala, Option(identity(1)) == identity(Option(1))
  • Composición de morfismos: Si tenemos dos morfismos f: A => B y g: B => C, se debe cumplir que F[f o g] = F[f] o F[g]. Que no es tan complicado si lo vemos con
    val f: Int => String = _.toString
    val g: String => Boolean = _.length > 1
    val l: List[Int] = List(1,20,3)
    l.map(f).map(g) ==
      l.map(f andThen g) ==
      l.map(n => g(f(n)))
    //List(false, true, false)
    

Pero a grandes rasgos, podemos pensar en los functores ordinarios como la descripción de como, en una cadena de producción o montaje F[_], se permite realizar transformaciones de manera que, para argumento un A, y usando un morfismo (una función de transformación) A => B, obtenemos un B dentro de la misma cadena de producción F[_]

Clasificación de functores

Los functores ordinarios también son denóminados co-variantes (para ser diferenciados de los contravariantes y de los invariantes). Veamos a continuación qué caracteriza a estos otros tipos de functor:

Functor contravariante

La definición formal dice que un functor para F[_] es contravariante si, en vez el método map, tiene definida la operación contramap:

trait Contravariant[F[_]]{
  def contramap[A, B](fA: F[A])(f: B => A): F[B]
}

Esto es, si existe una función B => A, el functor define F[A] => F[B].

…venga va, sin ser hardcore, ponemos un ejemplo.

Imagina una type class que sabe comparar elementos de un cierto tipo:

type Comparison = Int
val Greater = 1
val Equal = 0
val Lower = -1

trait Comparator[T]{
  def compare(t1: T, t2: T): Comparison
}

Imagina del mismo modo que yo dispongo de un comparador de números enteros (y aquí viene el quid de la cuestión):

si yo se comparar enteros y se como transformar ‘pepinos’ a enteros, ya se como comparar ‘pepinos’

object ComparatorF extends Contravariant[Comparator]{
  def contramap[A, B]
    (fA: Comparator[A])
    (f: B => A): Comparator[B] =
    new Comparator[B]{
      def compare(t1: B, t2: B): Comparison =
        fA.compare(f(t1), f(t2))
    }
}

Y ahora el archi-esperado ejemplo de generar un functor contravariante para pepinos:

trait Cucumber
val intC: Comparator[Int] = ???
val cucumberToInt: Cucumber => Int = ???
val cucumberC: Comparator[Cucumber] =
  ComparatorF.contramap(intC)(cucumberToInt)

cucumberC.compare(new Cucumber{}, new Cucumber{})

…sublime…

Functor invariante

Los functores invariantes para F[_] se caracterizan por tener un método denominado imap como sigue:

trait Invariant[F[_]] {
  def imap[A, B](fA: F[A])(f: A => B)(g: B => A): F[B]
}

…otra vez en el valle de la duda después de esta definición, lo se y pido paciencia. Se ve mucho mejor con otro ejemplo.

Imagina una type class que sabe almacenar objetos en una base de datos (MongoDB, por ejemplo):

case class ObjectId(hash: String)

trait DataStorage[T]{
  def store(t: T): ObjectId
  def get(id: ObjectId): T
}

Olvidándonos de los posibles efectos (excepciones, timeouts, etc) para no liar el ejemplo, podemos definir un functor invariante para DataStorage que permita almacenar cualquier elemento:

object DataStorageF extends Invariant[DataStorage]{
  def invariant[A, B]
    (fA: DataStorage[A])
    (f: A => B)
    (g: B => A): DataStorage[B] = {

    new DataStorage[B]{
      def store(b: B): Option[ObjectId] =
        fA.store(g(b))
      def get(id: ObjectId): B =
        f(fA.get(id))
    }
  }
}

Por lo tanto…

Si yo se como almacenar enteros y se transformar pepinos a enteros (y viceversa),
¡se cómo almacenar pepinos!

val intDS: DataStorage[Int] = ???
val cucumberToInt: Cucumber => Int = ???
val intToCucumber: Int => Cucumber = ???
val cucumberDS: DataStorage[Cucumber] =
  DataStorageF
    .imap(intDS)(intToCucumber)(cucumberToInt)

val id = cucumberDS.store(new Cucumber{})
val originalCucumber = cucumberDS.get(id)

No podemos decir que esto sea todo, pero sí puede ser una buena introducción al maravilloso mundo de los functores. Nos vemos en el próximo post. ‘Like’ y comparte si te gusta almacenar pepinos.
¡Agur de limón!

Fuentes:
[1] Advanced Scala with Cats – Noel Welsh & Dave Gurnell
[2] Variance and functors – Typelevel blog

Monoids are not demode and thursdays are actually the new fridays

No, it’s not the latest Coixet’s movie title. When we talk about cathegories like monoids, we tend to think they just remain on the edge as something merely experimental (even though purely functional) and there’s no direct application at the real world. Something similar happened when you learnt square roots at school and there was no proper moment to start using them at real life…

The use case

This morning, ah naïve me, I tried to make this work:

type Passengers = Int
type MaxCapacity = Passengers
type Plane = (Passengers, MaxCapacity)

val planes: List[Plane] =
  List(1 -> 1, 2 -> 3, 3 -> 3)

val (totalPassengers, totalCapacity) = 
  planes.sum 
//  ERROR: Could not find implicit value 
//  for parameter num: Numeric[(Int, Int)]

Ok, fair enough, Scala needs something, an evidence, for adding integer tuples.
Before we start fighting with the ‘evidence’, let’s try to make it work in a more mechanical way:

val sum = ((0,0) /: planes){
    case ((totalPas,totalCap), (passengers, capacity)) => 
      (totalPas + passengers, totalCap + capacity)
  }

Okay, it works. But it should be simpler, so let’s get back to the idea of Numeric[N] evidence.

Implementing Numeric[N]

We need a Numeric[(A,B)] but before implementing it (it has a lot of abstract methods) let’s sweep under the rug all those methods we don’t really want to focus on in this example. For doing so, let’s create an intermediate layer that keeps all those method without an implementation (which doesn’t mean ‘abstract’):

trait LeanNumeric[T] extends Numeric[T] {
  override def fromInt(x: Int): T = ???
  override def toInt(x: T): Int = ???
  override def minus(x: T, y: T): T = ???
  override def times(x: T, y: T): T = ???
  override def negate(x: T): T = ???
  override def toLong(x: T): Long = ???
  override def toFloat(x: T): Float = ???
  override def toDouble(x: T): Double = ???
  override def compare(x: T, y: T): Int = ???
}

Let’s call this abomination LeanNumeric (it only contains the essentials to develop our example). And now, we can define the method that generates the evidence for any Tuple2:

implicit def numeric[A, B](
  implicit nA: Numeric[A],
  nB: Numeric[B]): Numeric[(A, B)] = {
  new LeanNumeric[(A, B)]{
    override def zero = (nA.zero, nB.zero)
    override def plus(x: (A, B), y: (A, B)): (A, B) = {
      val (a1, b1) = x
      val (a2, b2) = y
      (nA.plus(a1, a2), nB.plus(b1, b2))
    }
  }
}

If we put the implicit into scope and we run again planes.sum…boom! Magic.

Num…oid

We don’t have to master category theory to realize that Numeric[N] may be a thousand of things, but it satisfies at least two properties:

  • The append operation: The sum operation – given n1 and n2 of type N, it return a new N element. Only because of this feature(and the closure, associativity, commutative, …) we can consider it a Semigroup.

  • And additionally the zero element

Seriously? Isn’t it obvious enough? My dear friends, monoid is back in town!

Implementation with scalaz.Monoid

Having in mind that Numeric has (at least) these two properties, let’s re-implement the implicit by using scalaz Monoid. We first define the monoid for integers and the tuple monoid which requires a monoid for each type that compounds the tuple (easy peasy):

import scalaz._

implicit object IntMonoid extends Monoid[Int]{
  override def zero: Int = 0
  override def append(f1: Int, f2: => Int): Int = f1 + f2
}

implicit def tupleMonoid[A,B](
  implicit mA: Monoid[A],
  mB: Monoid[B]): Monoid[(A,B)] = {
  new Monoid[(A, B)] {
    override def zero: (A, B) = (mA.zero, mB.zero)
    override def append(f1: (A, B), f2: => (A, B)): (A, B) = {
      val (a1, b1) = f1
      lazy val (a2, b2) = f2
      (mA.append(a1,a2), mB.append(b1, b2))
    }
  }
}

So good so far, right?

After that, we implement the implicit that will provide a Numeric as long as there’s a Monoid for the type

implicit def numeric[T](
  implicit m: Monoid[T]): Numeric[T] = {
  new LeanNumeric[T]{
    override def zero = m.zero
    override def plus(x: T, y: T): T = m.append(x, y)
  }
}

planes.sum //(6,7)

And it’s awesomely easy to get abstract of whatever T means (a tuple? a dog? …). As long as it’s a monoid, you can define a LeanNumeric.

You can find here the gist.

See you in the next functional madness.

Peace out!

Los monoides no están demodé y los jueves sí son los nuevos viernes

No, no es el título de la nueva película de Coixet. Cuando hablamos de categorías como los monoides, tendemos a pensar que se quedan en el ámbito de lo experimental (aunque funcionalmente puro) y que no existe aplicación directa sobre el mundo real. Algo parecido a lo que ocurría cuando te enseñaban a hacer raíces cuadradas en el colegio y no veías el momento para usarlo en la vida real…

El caso de uso

Esta misma mañana, ingenuo de mi, intentaba hacer que funcionara lo siguiente:

type Passengers = Int
type MaxCapacity = Passengers
type Plane = (Passengers, MaxCapacity)

val planes: List[Plane] =
  List(1 -> 1, 2 -> 3, 3 -> 3)

val (totalPassengers, totalCapacity) = 
  planes.sum 
//  ERROR: Could not find implicit value 
//  for parameter num: Numeric[(Int, Int)]

Vale, comprensible, Scala necesita algo, una evidencia, para poder sumar tuplas de enteros.
Antes de pegarnos con la “evidencia”, intentemos hacerlo funcionar de una manera mucho más mecánica:

val sum = ((0,0) /: planes){
    case ((totalPas,totalCap), (passengers, capacity)) => 
      (totalPas + passengers, totalCap + capacity)
  }

Okay, funciona. Pero debería ser algo más simple, por lo que retomemos la idea de la evidencia Numeric[N].

Implementando Numeric[N]

Necesitamos un Numeric[(A,B)] pero antes de implementarlo (tiene bastantes métodos abstractos) escondamos debajo de la alfombra aquellos métodos en los cuales no queremos centrarnos en este ejemplo. Para ello creamos una capa por encima que deje los métodos sin implementar (que no abstractos):

trait LeanNumeric[T] extends Numeric[T] {
  override def fromInt(x: Int): T = ???
  override def toInt(x: T): Int = ???
  override def minus(x: T, y: T): T = ???
  override def times(x: T, y: T): T = ???
  override def negate(x: T): T = ???
  override def toLong(x: T): Long = ???
  override def toFloat(x: T): Float = ???
  override def toDouble(x: T): Double = ???
  override def compare(x: T, y: T): Int = ???
}

A esta aberración la llamaremos LeanNumeric (solo contiene lo esencial para desarrollar nuestro ejemplo). Y ahora definimos el método que genera la evidencia para cualquier Tuple2:

implicit def numeric[A, B](
  implicit nA: Numeric[A],
  nB: Numeric[B]): Numeric[(A, B)] = {
  new LeanNumeric[(A, B)]{
    override def zero = (nA.zero, nB.zero)
    override def plus(x: (A, B), y: (A, B)): (A, B) = {
      val (a1, b1) = x
      val (a2, b2) = y
      (nA.plus(a1, a2), nB.plus(b1, b2))
    }
  }
}

Si ponemos el implícito en scope y ejecutamos de nuevo el planes.sum…¡boom! Magia.

Num…oide

No hace falta saber mucho de teoría de categorías para centrarse en que Numeric[N], podrá ser mil cosas más, pero cumple dos propiedades:

  • La operación append : La operación suma – dados n1 y n2 de tipo N, devuelve otro elemento N. Solo por disponer de esta operación (y el clossure, la asociatividad, la conmutativa, …) ya podemos considerarlo un Semigroup

  • Y adicionalmente el elemento zero : genera el elemento neutro de la suma

¿en serio?¿No es evidente?…amigos, ¡el monoide ha vuelto a la ciudad!

Implementación con scalaz.Monoid

Viendo que el tipo Numeric tiene al menos esas dos propiedades, re-implementamos el implícito haciendo uso de Monoid. Primero definimos el monoide para enteros y el monoide de las tuplas,
el cual requiere de un monoide para cada tipo que compone la tupla (easy peasy)

import scalaz._

implicit object IntMonoid extends Monoid[Int]{
  override def zero: Int = 0
  override def append(f1: Int, f2: => Int): Int = f1 + f2
}

implicit def tupleMonoid[A,B](
  implicit mA: Monoid[A],
  mB: Monoid[B]): Monoid[(A,B)] = {
  new Monoid[(A, B)] {
    override def zero: (A, B) = (mA.zero, mB.zero)
    override def append(f1: (A, B), f2: => (A, B)): (A, B) = {
      val (a1, b1) = f1
      lazy val (a2, b2) = f2
      (mA.append(a1,a2), mB.append(b1, b2))
    }
  }
}

Hasta aquí bien, ¿correcto?

Implementemos ahora el implícito que nos proporcionará un Numeric siempre que exista un Monoid para el tipo

implicit def numeric[T](
  implicit m: Monoid[T]): Numeric[T] = {
  new LeanNumeric[T]{
    override def zero = m.zero
    override def plus(x: T, y: T): T = m.append(x, y)
  }
}

planes.sum //(6,7)

Y es increiblemente sencillo abstraerse de lo que demonios sea T (¿una tupla?, ¿un perro?, …).
Mientras sea un monoide, se puede definir un LeanNumeric.

Podéis encontrar aquí el gist resumen

Hasta la próxima locura funcional.

¡Agur de limón!