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!

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!

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!

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

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!

Scalera tip: contextos implícitos pegajosos

El otro día (para la gente normal: hace cosa de 1 mes) vi que el gran Viktor Klang twiteaba acerca de como quitar las molestas evidencias implícitas en definiciones de métodos. Y me pareció tan elegantes algunas de las cosas que leí, que me vi en la obligación de compartir algunas ideas al hilo de dichos consejos con aquellos de vosotros que no le sigáis aun en Twitter (@viktorklang).

La situación

Imaginad el típico método polimórfico en el cual necesitamos un execution context para ejecutar un futuro:

import scala.concurrent.{ExecutionContext, Future}

def myMethod[T]
  (element: T)
  (implicit ev: ExecutionContext): Future[Boolean] = ???

Es tan típico como feo, el tener que repetir la coletilla de (implicit ev: ExecutionContext) en 10 métodos seguidos…

Jugando con type alias

La idea feliz que se propone es definir un type alias del siguiente tipo:

type EC[_] = ExecutionContext

De esta forma, re-definiríamos la cabecera de nuestro método como sigue:

def myMethod[T:EC](element: T): Future[Boolean] = ???
myMethod("hi")

¿Bello o no?

Otras posibilidades

Métodos no polifórmicos

En el caso en que nuestro método no esté parametrizado, tendríamos que añadir algo de boilerplate (añadiendo un wildcard para el tipo que parametriza el método), pero en esencia debería seguir funcionando el mismo principio:

def myMethod[_:EC](element: Int): Future[Boolean] = ???
myMethod(2)

Múltiples contextos implícitos

En el no-tan-descabellado caso en el que necesitáramos varios parámetros implícitos de distintos tipos, necesitaríamos definir tantos type alias como tipos distintos de parámetros requiriésemos:

type EC[_] = ExecutionContext
type MongoDB[_] = MongoDBDatabase

def myMethod[_:EC:MongoDB](element: Int): Future[Boolean] = ???

Pero, ¿y si…?

Múltiples parámetros implícitos del mismo tipo

En el caso de que tengamos múltiples parámetros implícitos del mismo tipo,

def myMethod
  (element: Int)
  (implicit ev1: ExecutionContext, ev2: ExecutionContext): Future[Boolean] = ???

ocurriría que …

Bueno, por definición eso es imposible ya que incurriría en un problema de ambigüedad a la hora de resolver implícitos. Es cierto que Scala nos permite este tipo de signaturas, pero sólo podríamos invocar al método haciendo explícitos los argumentos del segundo grupo de parámetros:

myMethod(2)(ec1,ec2)

Lo cual es un tanto…

Contextos implícitos que son constructores de tipos

Cuando tenemos parámetros implícitos que son constructores de tipos como List[T], Future[T], Option[T]

En realidad depende.

Caso1

Si el tipo que parametriza el método y el que parametriza la evidencia no están relacionados, no hay mucho problema: definimos otro type alias y a correr:

type EC[_] = ExecutionContext
type MongoDB[_] = MongoDBDatabase
type IntOpt[_] = Option[Int]
type StrList[_] = List[String]

def myMethod[_:EC:MongoDB:IntOpt:StrList](
  element: Int): Future[Boolean] = ???

Lo cual sería el equivalente a:

def myMethod(
  element: Int)(
  implicit ev1: ExecutionContext,
  ev2: MongoDBDatabase,
  ev3: Option[Int],
  ev4: List[String]): Future[Boolean] = ???

Caso 2

Si el tipo que parametriza el método y el que parametriza la evidencia tienen que concordar …

Bueno no es posible. El syntax sugar implica que el tipo que parametriza el método vaya en concordancia con el tipo que parametriza nuestra evidencia. Quizás era todo demasiado bonito 🙂

Hasta el próximo post. ¡Agur de limón!

Scalera tip: Mantén estado en tu actor sin usar un solo VAR

Es de todos sabido que usar VARs es algo que, aparte de mal visto, está mal, es el infierno en vida, hace que mueran gatitos y muchas otras perlitas que probablemente ya habréis oído antes y que por poco os ha causado una muerte lenta y dolorosa en el cadalso.

La esencia en programación funcional es, por tanto, la inmutabilidad: cada vez que muto un elemento, genero uno nuevo.

What about Akka actors?

Cuando hablamos de actores, podemos definirlos como unidades con estado que procesan de manera secuencial una cola de mensajes, asociando (o no) a cada uno de estos mensajes una cierta lógica computacional.

Siempre se ha dicho que para mantener dicho estado dentro de la lógica de un actor, no pasaba nada si usabas un var:

Es imposible que hayan problemas de concurrencia: solo el propio actor tiene acceso a dicho VAR y procesará un solo mensaje al mismo tiempo.

Pero quizás podamos renunciar a esta premisa si buscamos una manera de redefinir el comportamiento del actor en base a un nuevo estado.

Mortal approach

Siguiendo la filosofía antes descrita, la primera (y más directa) aproximación para mantener el estado en un actor se parecería bastante a lo siguiente:

class Foo extends Actor{
  var state: Int = 0
  override def receive = {
    case Increase => state += 1
  }
}

Cada vez que llega un mensaje Increase, modificamos el valor de state, sumando 1.
Hasta aquí nada complicado, ¿no?

Immutable approach

Sin embargo, podríamos definir una función receive que estuviera parametrizada por un cierto estado, de manera que cuando llegue un mensaje, el estado a tener en cuenta sea este parámetro.

Si se diera la circunstancia de tener que actualizar el valor de dicho estado, bastaría con invocar al método become que modifica el comportamiento del actor. En nuestro caso, dicha modificación del comportamiento consistiría en cambiar el valor del estado.

Si usamos el mismo ejemplo que antes:

class Foo extends Actor{
  def receive(state: Int): Receive = {
    case Increase =>
      context.become(
        receive(state + 1),
        discardOld = true)
  }
  override def receive = receive(0)
}

vemos que la función que define el receive en base al estado recibe un parámetro denominado state. Cuando llega un mensaje de tipo Increase, lo que hacemos es invocar a become para modificar el comportamiento del actor, pasando como argumento el nuevo estado a tener en cuenta.

Si queremos mejorar un poco la legibilidad, podríamos incluso abstraer todo actor con estado actualizable:

trait SActor[State] extends Actor {
  val initialState: State
  def receive(state: State): Receive
  def update(state: State): Receive =
    context.become(
      receive(state),
      discardold = true)
  override def receive =
    receive(initialState)
}

de manera que se especifique el estado inicial del actor, una nueva función de receive que queda parametrizada por el nuevo estado a gestionar, y una nueva función de update que se encarga de realizar la llamada a become como antes explicábamos.
Con todo ello nos queda un nuevo actor Foo mucho más curioso:

class Foo extends SActor[Int] {
  val initialState = 0
  def receive(state: Int): Receive = {
    case Increase => update(state +1)
  }
}

Potential hazardous issues

Nótese que en el ejemplo de antes hemos pasado un segundo argumento: discardOld = true. Este argumento indica si el comportamiento nuevo debe apilarse sobre el anterior o si por el contrario debe sustituirlo por completo.

Supongamos que usáramos un discardOld = false. Si cada vez que llegase un mensaje de incremento, apilásemos un nuevo comportamiento, podríamos llegar a tener un problema de desbordamiento.

Hasta el próximo briconsejo.

Agur de limón 🙂

Más lazy’s, la mónada State y otras cosas con estado

En el anterior post hablábamos sobre la evaluación perezosa en Scala. Al final de dicho post, planteábamos una pregunta: ¿Un Lazy tiene estado?

24195622

Para responder a dicha pregunta, vamos a intentar definir un tipo que represente un valor Lazy como sigue:

trait Lazy[T] {

  val evalF : () => T

  val value: Option[T] = None

}
object Lazy{
  def apply[T](f: => T): Lazy[T] =
    new Lazy[T]{ val evalF = () => f }
}

Como se puede observar, nuestro tipo Lazy está parametrizado por un tipo T que representa el tipo del valor en cuestión(Lazy[Int] sería la representación de un entero perezoso).
Además, podemos ver que se compone de dos elementos principales que caracterizan a un Lazy:

  • evalF : Función de cero argumentos que, al invocar su método apply, evalúa la expresión de T contenida.
  • value : El valor resultante de la interpretación de la función evalF. Esta parte es la que denota el estado en el tipo Lazy, y solo admite dos posibles valores: None (no evaluado) o Some(t) (si ya ha sido evaluado y el resultado obtenido).

También hemos añadido un objeto companion que define el constructor de instancias Lazy que recibe un argumento by-name que se devuelve como resultado de la función evalF.

e9a2295b3db9b45c8f5484a09033c1c71cf88e3375bb7ff60456bc81c29a4e04

La cuestión ahora es: ¿Cómo unimos la función de evaluación con el valor que devuelve para hacer que Lazy mantenga un estado? Definiendo la función eval:

trait Lazy[T] { lzy =>

  val evalF : () => T

  val value: Option[T] = None

  def eval: (T, Lazy[T]) = {
    val evaluated = evalF.apply()
    evaluated -> new Lazy[T]{ mutated =>
      val evalF = lzy.evalF
      override val value = Some(evaluated)
      override def eval: (T, Lazy[T]) = 
        evaluated -> mutated
    }
  } 

}

La función eval devuelve una tupla de dos elementos:

  • el valor resultante de la evaluación de la expresión que representa el valor perezoso.
  • una nueva versión del valor Lazy que contiene el nuevo estado: el resultado de la evaluación.

Si os fijáis, lo que hace el método en primer lugar, es invocar a la función evalF para obtener el valor de tipo T que aún estaba sin evaluar.
Una vez hecho esto, lo devolvemos así como la nueva versión del elemento Lazy. Esta nueva versión (llamémosla mutated) tendrá en su atributo value el resultado de haber invocado a evalF. Del mismo modo, modificamos su método eval, para que en sucesivas invocaciones se devuelva a sí mismo y no genere nueva instancias que en realidad no varían su estado.

La cuestión interesante viene ahora: ¿es este un caso único? ¿Existen más ‘cosas’ que mantienen un estado? Hagamos un ejercicio de abstracción.

Buscando la genericidad: cosas-con-estado

Pensemos en el caso de una pila:

sealed trait Stack[+T]
case object Empty extends Stack[Nothing]
case class NonEmpty[T](head: T, tail: Stack[T]) extends Stack

La implementación sale casi sola. Pero centrémonos en el trait Stack y en un hipotético método pop que desapila un elemento que se devuelve junto al resto de la pila:

sealed trait Stack[+T]{
  def pop(): (Option[T], Stack[T])
}

¿Os suena de algo? ¿No se parece misteriosamente a

trait Lazy[T]{
  def eval: (T, Lazy[T])
}

…?

Si intentamos sacar factor común entre Lazy y Stack podríamos definir un tipo mucho más abstracto llamado State:

trait State[S,T] {
  def apply(s: S): (T, S)
}

Simple pero bello: el trait State está parametrizado por dos tipos: S (tipo de estado) y T (información o elemento adicional que devuelve cada vez que mutamos el estado). Aquí donde lo veis, se trata de un patrón muy recurrente al diseñar sistemas en Scala. Siempre hay algo que mantiene un estado. Y todo lo que tiene estado muta. Y si ese algo muta de manera segura y elegante…oh man.

Esto ya existe …

21495586

Toda esta historia que parece sacada de un ensayo post-moderno, resulta que ya ha sido objeto de estudio de personas que estudian cosas. Sin entrar en mucho detalle, en la librería ScalaZ podéis encontrar la mónada State que, además de lo descrito anteriormente, trae de serie un full-equipped de componibilidad y todo lo que conlleva ser Mónada (semigrupo, monoide, etc).

Si definimos nuestro tipo Lazy con la mónada State tenemos algo como:

import scalaz.State

type Lazy[T] = (() => T, Option[T])

def Lazy[T](f: => T) = (() => f, None)

def eval[T] = State[Lazy[T], T]{
  case ((f, None)) => {
    val evaluated = f.apply()
    ((f, Some(evaluated)), evaluated)
  }
  case s@((_, Some(evaluated))) => (s, evaluated) 
}

Al descomponer el jeroglífico egipcio arriba expuesto, dada la mónada State[S,T], nuestro estado S va a ser una tupla de lo que representa en el fondo a una evaluación perezosa:

type Lazy[T] = (() => T, Option[T])

y que más arriba hemos descrito:

  • Una Function0 que representa la evaluación demorada de T
  • El valor T que puede haberse evaluado o no

Para construir un valor Lazy, generamos una tupla con una función que recoge la expresión indicada por un argumento by-name del método Lazy y el valor None (porque aún no ha sido evaluado el Lazy):

def Lazy[T](f: => T) = (() => f, None)

Por último (y esta es la parte importante) definimos la única transición posible de estado que podemos concebir cuando hablamos de valores perezosos: la evaluación. Esta es la clave cuando diseñamos cualquier constructor de tipos que extiende de State: lo importante es modelar qué es nuestro tipo S y las transiciones de estado posibles.

Para el tipo Lazy, tenemos dos posibles casos: que la expresión aún no haya sido evaluada (en cuyo caso la evaluamos y devolvemos la misma función y el resultado) ó que la expresión ya haya sido evaluada (en cuyo caso dejamos el estado como está y devolvemos además el resultado de la evaluación):

def eval[T] = State[Lazy[T], T]{
  case ((f, None)) => {
    val evaluated = f.apply()
    ((f, Some(evaluated)), evaluated)
  }
  case s@((_, Some(evaluated))) => (s, evaluated) 
}

iZcUNxH

Para comprobar que seguimos contando con las mismas características iniciales para las que definimos el tipo Lazy (solo se evalúa una vez, solo se evalúa cuando es necesario, …) lanzamos las siguiente aserciones:

var sideEffectDetector: Int = 0

val two = Lazy {
  sideEffectDetector += 1
  2
}

require(sideEffectDetector==0)

val (_, (evaluated, evaluated2)) = (for {
  evaluated <- eval[Int]
  evaluated2 <- eval[Int]
} yield (evaluated, evaluated2)).apply(two)

require(sideEffectDetector == 1)
require(evaluated == 2)
require(evaluated2 == 2)

Si os fijáis, como antes comentábamos, lo que se define en la for-comprehension son las transiciones o pasos que va a enfrentar el estado que nosotros queramos. Es decir, definimos las mutaciones que sufrirá un estado S cualquiera. Una vez definida la ‘receta’, la aplicamos al estado inicial que nosotros queramos.
En este caso, definimos como estado inicial un perezoso número entero dos. Para comprobar el número de veces que se evalúa nuestro Lazy, añadimos un var muy dummy que funcionará a modo de contador. Luego definimos en nuestra ‘receta’ que el estado debe mutar dos veces mediante la operación eval. Posteriormente comprobamos que solo se ha ejecutado una vez la expresión del bloque Lazy y que el valor resultante de la expresión es el esperado.

Os deseo la mejor de las sales de frutas para digerir todo esto 🙂
Sentíos libres de añadir comentarios/amenazas en el post o en nuestro canal de gitter.

Hasta el próximo post.
¡Agur de limón!

Valores perezosos

Por si hubieras estado en un agujero durante los últimos 10 años y no lo supieras, Scala permite gestionar valores de evaluación perezosa.

image

En Scala, podemos definir un valor que no será evaluado hasta que se le llame de manera explícita. Por ejemplo:

lazy val myLazyInt: Int = { println("hi"); 2 }

Como podéis ver, usando la notación lazy hemos definido de manera perezosa un entero que vale 2 y que imprime un ‘hola’ cuando se evalúa.
Aparte de haber violado la gran ley de la programación funcional (transparencia referencial) debido al infame println, side effects, muerte, destrucción, blah blah …

anigif_enhanced-1822-1407333641-6

fijaros que si ejecutamos el fragmento de código, dicho println no se ejecuta.
No es sino hasta que otra expresión hace uso de nuestro entero perezoso, que no se ejecuta el bloque:

val result = myLazyInt + 3
//woa! somebody printed 'hi' and I have a brand new 5 inside 'result'

Una vez calculado myLazyInt, su valor no volverá a calcularse independientemente de cuantas veces se invoque. Es decir, ya no volverá a aparecer una misteriosa impresión que nos saluda:

lazy val myLazyInt: Int = { println("hi"); 2 }
myLazyInt
//"hi"
myLazyInt //nothing special happened now ...
myLazyInt //no matter how many times you invoke it...
myLazyInt //seriously, let it go...

Curioso. La cuestión es, si yo defino un valor perezoso y lo paso a un método como argumento, ¿qué ocurre? ¿Se evalúa en el momento en que se invoca la función?¿Quizás dentro del cuerpo de la función? Eso dependerá de cómo definas los argumentos de tu método.

Call by name vs. call by value

Al definir un método, por lo general, definimos sus argumentos ‘by-value’, es decir, esperamos que el argumento ya se encuentre evaluado al pasarse al método:

def myMethod(someInteger: Int): Int = {
  println("begin")
  val result = someInteger + 2
  println("end")
  result
}

Si invocamos nuestro método con un número entero cualquiera:

val n = 3
val result = myMethod(n)
//"begin"
//"end"
require(result == 5)

Imprimimos nuestras dos trazas y ya está. Hasta aquí nada nuevo.
¿Qué ocurre ahora si le pasamos nuestro valor perezoso?¿En qué momento imprimirá “hi”?¿Antes o después de las trazas del método?
Probemos:

myMethod(myLazyInt)
//"hi"
//"begin"
//"end"

Lo imprimió antes, es decir, nuestro valor perezoso se evaluó antes de invocarse el método. ¿Esto por qué ocurre? Porque Scala, para poder ejecutar myMethod, necesita saber el valor de someInteger.
Es un fastidio si queremos mantener la evaluación de myLazyInt perezosa hasta el final. ¿Cómo lo solucionamos? Pasando el argumento ‘by-name’, es decir, indicando cómo se resolverá en el futuro el valor, pero sin pasar el valor de manera explícita:

def myMethod(someInteger: => Int): Int = {
  println("begin")
  val result = someInteger + 2
  println("end")
  result
}

De esta forma (someInteger: => Int) indicamos que le vamos a nuestro método como argumento una expresión que devolverá un entero (que no un entero). Si ahora ejecutamos el método pasándole nuestro valor perezoso no-evaluado:

myMethod(myLazyInt)
//"begin"
//"hi"
//"end"

Voilà! No es hasta el último momento en que se requiere el valor dentro del método, que no se evalúa nuestro entero perezoso.

Otras formas de expresar laziness

Otra forma que nos puede resultar muy útil para denotar que una expresión se evalúa de manera perezosa, es el tipo Function0:

trait Function0[+R]{
  def apply(): R
}

Se trata de una función que recibe 0 argumentos y devuelve un tipo de salida. Normalmente se suele notar como sigue:

val f: () => Int =
  () => 2
f.apply() //2

No hay mucho más misterio…Una vez comprendido a grandes rasgos el funcionamiento de la evaluación perezosa en Scala, pasemos a cuestiones más interesantes…¿Un Lazy es algo con estado?
La respuesta (o más preguntas) en el próximo post.

¡Agur de limón!

Tipos de datos algebraicos en Scala

Qué mejor que volver de vacaciones con las pilas cargadas y con algún que otro tornillo suelto que nos empuje a escribir sobre temas que solo se te ocurren bajo el influjo de los puerros la luna.

¿TDA?

Un Tipo de Datos Algebraico(TDA en adelante para que no nos cobre WordPress por palabra) no es sino expresar un tipo de datos (Gato, Coche, Prevaricación) en base a un álgebra. Y cuando decimos álgebra nos referimos a sumas y productos de tipos (de Enteros, Gatos, Coches, Prevaricaciones, …). Por ejemplo:

Train = Locomotive + Wagon * Train

¿Esto como se lee? Un tren puede ser: una locomotora O un vagón Y otro tren (que a su vez puede ser otro vagón y otro tren, que a su vez …).
Fijaos en la disyunción y la conjunción: la suma suele representar un OR y el producto un AND (como en el álgebra de Boole).

Es interesante también darse cuenta que, de esta definición de tipos, se puede inferir un patrón recursivo. En el caso del tren, el caso base es la locomotora y en el caso recursivo tenemos un vagón y otro tren. Como veremos más adelante, este patrón se repite y facilita la definición de tipos.

¿Y cómo se representa la suma y el producto en Scala?

La forma más sencilla de representar la suma (también llamada coproducto) de tipos, en un paradigma que soporte polimorfismo (en general) y en Scala (en particular), no es sino la herencia. Si tenemos el siguiente caso:

sealed trait Animal
case object Cat extends Animal
case object Dog extends Animal

estamos formulando un coproducto de tipos:

Animal = Cat + Dog

es decir, un Animal solamente puede ser, o un Cat, o un Dog.

En cuanto al producto, podríamos definirlo como el conjunto de atributos que componen una instancia de un cierto tipo. Por ejemplo,

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

expresado como suma de productos, es como sigue:

Student = String * Int

Es decir, para construir el tipo Student hace falta un String y un Int.

Si ahora tratamos de bajar a tierra el modelo de tren antes propuesto (con algún aditivo) tendremos que

Wagon = String * Int
Train = Locomotive + Wagon * Train

se traduce en Scala a

sealed trait Train
case object Locomotive extends Train
case class Wagon(model: String, passengers: Int)
case class Nexus(wagon: Wagon, next: Train)

¿Y esto para qué?

Si piensas, amigo, que esto son cosas que nadie usa, es porque no te paraste a pensar en qué estructuras de scala.predef se definen de esta forma. Las listas (List) por ejemplo se definen como:

trait List[+T]
case object Nil extends List[Nothing]
case class ::[T](head: T, tail: List[T]) extends List[T]

Es decir, una lista puede ser, o lista vacía, o un elemento seguido de otra lista.
Si lo expresamos en función de productos y coproductos:

List[T] = EmptyList[T] + NonEmptyList[T]
NonEmptyList[T] = T * List[T]

Fijaos que el caso de la lista vacía (Nil) tiene una implementación muy bonita en Scala.

Si tenemos que definir una lista vacía para tooooodos los tipos existentes, tendríamos que instanciar un Nil[Cat], Nil[Dog], …
Para evitar eso, y tener un único Nil, hacemos que este extienda de List[Nothing] que, como recordareis de otros posts, Nothing extiende de tooooodos los tipos (tanto primitivos como definidos por el programador). Si a esto le sumamos que List[T] es covariante en T, tenemos un único objeto Nil que representa las listas vacías de tooooodos los tipos. Alucinante, ¿no?

odtUdEE

Ejemplo: Números pares

Para afianzar esta novedosa forma de pensar, pongámonos en la siguiente tesitura, ¿cómo podríamos representar los números pares en Scala?

Requirements

Si somos poco delicados y confiamos más en las aserciones en tiempo de runtime, podríamos decir que los números pares son:

case class Even(value: Int) { 
  require(value%2==0, "it's not even")
}

Si intentamos crear un Even con un número impar nos dirá que nope:

Even(1)
java.lang.IllegalArgumentException: requirement failed: it's not even
	at scala.Predef$.require(Predef.scala:233)
	at Even.<init>(<console>:7)

Sin embargo esta comprobación no se realiza hasta el momento de ejecución, que es cuando se comprueba el require. Por lo que nuestro código podría estar compilando pero no ser correcto…
Podemos hacerlo mejor…

Next(Next(…))

Otra opción es asumir (y no vamos a discutir sobre ello) que el número 0 es par, que tenemos memoria infinita en nuestra máquina, que no existe el overflow, …

907958

En ese caso, para nada alejado de la realidad (…) podríamos definir los números enteros pares como:

sealed abstract class Even(val value: Int)
case object Zero extends Even(0)
case class Next(previousEven: Even) 
  extends Even(2 + previousEven.value)

De manera que si tenemos un método que genera una reserva para el barco del amor que requiere de un número par de participantes, podemos usar nuestro recién definido tipo Even:

def loveBoatReservation(
  peopleAmount: Even): Reservation = ???

Dado que no hay forma de construir un Even a partir de un entero que no sea par, evitamos situaciones en runtime en las que el número de personas que se montan en el barco sean impares. Sino siempre habría alguien …

forever-alone-400x400

Mecánica de métodos sobre TDAs recursivos

Una vez definido el tipo de datos, supongamos que queremos implementar la suma de números pares:

def sum(e1: Even, e2: Even): Even = ???

Tenemos varias alternativas. Una de ellas puede ser la quick-and-dirty:

def sum(e1: Even, e2: Even): Event = 
  new Even(e1.value + e2.value){}

Pero fijaos que estamos pasando un kilo de los constructores que hemos definido. Si queremos hacer pattern matching ahora sobre el resultado:

val four = new Even(4){}
sum(Zero, four) match {
  case Zero => 
    //it never gets inside this case!
  case Next(Next(Zero)) => 
    //OMG! IT DOESN'T FIT HERE EITHER!
}
scala.MatchError: $anon$1@649f2009 (of class $anon$1)

51067781

La otra técnica (algo más fina por otra parte) consiste en lanzar un método recursivo que, en cada llamada, vaya disminuyendo el segundo número par mientras que aumenta el primero. Para ello hacemos uso del constructor y extractor Next:

def sum(e1: Even, e2: Even): Even = {
  @tailrec
  def rSum(ev1: Even, ev2: Even): (Even, Even) = {
    ev2 match {
      case Zero => (ev1, Zero)
      case Next(e) => rSum(Next(ev1), e)
    }
  }
  val (result, _) = rSum(e1, e2)
  result
}

Innegablemente bello 🙂

Conclusiones

Pues a parte de que los posts de vuelta de vacaciones suelen ser para volverse majara, saquemos varias conclusiones principales:

  • Como siempre decimos, que toda comprobación que nos podamos llevar de runtime a tiempo de compilación es un ahorro de quebraderos de cabeza cazando fallos con software en producción (lo cual es caro y es más fácil que haga rodar cabezas).
  • Que los constructores son la clave. Si definimos un TDA sobre los números pares, podemos controlar que los valores generados son correctos definiendo los constructores adecuados: el Zero y el Next. En ambos casos, tenemos la certeza de que se cumplen las leyes de los números enteros.
  • Que los métodos que operan sobre tipos de datos recursivos suelen ser, a menudo, recursivos también. Y no solo eso, sino que para poder generar valores del tipo en cuestión (Even en nuestro caso) solo deberían hacer uso de los constructores ya existentes.

En otro post hablaremos sobre la relación del álgebra de tipos y la definición de gramáticas formales…o no.

¡Agur de limón!