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!

Anuncios

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!

Abstract alge..what?: Monoids

We are like the fascicles of tin soldier collections and the free enrollment at gyms … back in September! And with something that seems a little harsh but that surely after reading a few lines will be easier. Today we initiate a series of posts in which we’ll discuss some functional abstractions, that are very related to the world of mathematics, with the aim of being gradually introduced into the world of purely functional Scala libraries, such as as Scalaz or Cats. To do this, we will rely on some features and techniques that have already been discussed, such as type classes.

Today, we’ll start with monoids.

Monoids, monoids everywhere…

Monoid? Weird word. Let’s see what Wikipedia has to say about monoids:

“Algebraic structure with a single associative binary operation and an identity element. Monoids are studied in semigroup theory as they are semigroups with identity.”

i-dont-understand

Hmmm… yes, right. What the hell? Let’s face it gradually.

  • Binary operation: ok, I get this one. It’s an operation with two operands. In order to follow the same convention, we are going to call this operation in a generic way with the symbol |+|.

However, the binary operation must comply with two rules:

  1. It must comply with the associative property, ie:  a |+| (b + c) = (a + b) |+| c
  2. And besides, it must have a neutral element.:   a |+| neutralElement = a

Hmmmm, that makes sense. For instance, the addition of integers follows the rules of monoids:

  • Associative property: 1 + (2 + 3) = (1 + 2) + 3
  • The neutral element is zero: 1 + 0 = 1

Same reasoning applies to the multiplication of integers (whose neutral element is 1), and the concatenation of strings as well (being the empty string “”, the neutral element). Great! I’m starting to get it.

Monoids in Scalaz

In order to work with monoids, in this post we will be using the Scalaz library. Scalaz offers us a wide range of options, abstractions and functionalities. Today we will only give you a glimpse of Scalaz in order to write an example. We promise to talk at great length about this library in the future 🙂

The first thing to do is to import Scalaz in our project. To do so, we have to include this library in the dependencies of our project:

libraryDependencies += "org.scalaz" %% "scalaz-core" % "7.1.3"

and now we write the following imports:

import scalaz._, Scalaz._

If we get a sneak peek at the library, we can find the Monoid  Monoid class, or the class Semigroup. They define the functionality we will make use of.

The Monoid class defines the zero method,, which serves us to indicate which is the neutral element. Furthermore, in the MonoidLaw trait, the fundamental rule that a neutral element must comply with is defined.

But, where is the information regarding the associative property?. Well, it is in the Semigroup class. It seems that monoids are an specialization of semigroups. Semigroups are responsible for the associative rule, while monoids add the rule of the neutral element. In fact, if we go to the Semigroup class, we can find the append method, which represents our binary operation |+|, and a bit further down we can see how the associative property is defined.

The next thing we must know is that Scalaz is based on a structure of type classes. This is why we will have to implement implicit objects, defining the inherent behaviour of our structures with respect to the monadic properties.

After this brief but (I hope) sufficient explanation, we will see how to apply all this knowledge to a simple example.

Creating our first monoid

Now finally, we will create our own monoid with Scalaz. Let’s start by creating new data types. We’ll create two types of atoms: oxygen and hydrogen.

trait Atom
case object Oxygen extends Atom
case object Hydrogen extends Atom

And finally, the molecule type. This type will have a certain number of atoms (which may be of different types), and a molecular stability.

case class Molecule(atoms: Map[Atom, Int], stability: Double)

jesse-pinkman-yeah-bitch-science

We want to work with our molecule type as if it were a monoid. For that purpose, and by using the Scalaz type classes, we’ll define an implicit object that defines the monadic behaviour of the molecule:

implicit object MoleculeIsMonoid extends Monoid[Molecule] {

  def zero: Molecule = Molecule(Map(), 1)
  def append(f1: Molecule, f2: => Molecule): Molecule = f1.fusion(f2)

}

We have decided that the neutral molecule is the one that has no atoms, and whose stability is 1, the maximum stability possible.

In addition, we have decided that the addition of molecules is delegated to a function called fusion. Let’s see how it’s defined:

case class Molecule(atoms: Map[Atom, Int], stability: Double) {

def fusion(other: Molecule): Molecule =
  Molecule(
    atoms = atoms |+| other.atoms,
    stability = stability * other.stability
  )
}

As we can see, the fusion of two molecules is the union of the atoms that compose them as if they were monoids. It turns out that Scalaz provides us with the functionality to address the combination of maps as monoids. In addition, we will combine the stabilities as if they were two probabilities, that is, multiplying them both.

The append method can also be represented by the symbol | + |, just as we had initially defined it.

And that’s all! We can now merge molecules using monoids and the operations that Scalaz provides us with for such abstractions:

val m1 = Molecule(Map(Oxygen -> 1, Hydrogen -> 2), 0.2)
val m2 = Molecule(Map(Oxygen -> 1), 0.3)

m1 |+| m2
//Molecule(Map(Oxygen -> 2, Hydrogen -> 2), 0.06)

List(m1, m2, m2, m1).foldLeft(Molecule(Map(), 1))(_ |+| _)
//Molecule(Map(Oxygen -> 4, Hydrogen -> 4), 0.0036)

Some(m1) |+| Some(m2)
//Some(Molecule(Map(Oxygen -> 2, Hydrogen -> 2), 0.06))

46196561

Conclusion

By the time you get to this point, I hope you’ll have lost the fear of bizarre words. As we have seen, algebraic abstractions are just a way of naming items that comply with some properties. Then, we can benefit from this abstraction to treat different operations in the same way. Today, we’ve seen monoids. Later on, other concepts you might have heard about will be discussed, such as the famous monads. Now you know, show off your knowledge about monoids to your friends and family 🙂

Teoría de Cate-movidas: Monoides

Somos como las colecciones de soldaditos de plomo y las matrículas gratis en los gimnasios… ¡volvemos en Septiembre!
Y volvemos con algo que parece muy heavy pero que seguro que después de leer unas pocas líneas será más sencillo.
Hoy comenzamos con una serie de post en las que hablaremos de algunas abstracciones funcionales, muy relacionadas con el mundo de las matemáticas, con el objetivo de introducirnos poco a poco en el mundo de las librerías puramente funcionales de Scala, como pueden ser Scalaz y Cats. Para ello, vamos a apoyarnos en algunas características y técnicas que ya hemos tratado anteriormente, como por ejemplo, las type classes.

Por ello, hoy vamos a comenzar con los monoides.

Monoides, monoides everywhere…

¿Monoide? Un palabro un poco raro. Vamos a ver que dice Wikipedia sobre los monoides:

“Estructura algebraica con una operación binaria, que es asociativa y tiene elemento neutro, es decir, es un semigrupo con elemento neutro.”

i-dont-understand

Ah…si, claro. Ya lo pillo…… Vamos a analizarlo poco a poco.

  • Operación binaria: ok, eso lo entiendo. Es una operación con dos operandos. Para tener un convenio, llamaremos a esta operación de forma genérica con el símbolo |+|

Sin embargo, la operación binaria tiene que cumplir dos reglas:

  1. Debe cumplir la propiedad asociativa, es decir:  a |+| (b + c) = (a + b) |+| c
  2. Y además, debe tener un elemento neutro:   a |+| elementoNeutro = a

Mmmm, ya está más claro. Entonces, la suma de enteros cumple las reglas de los monoides:

  • Cumple la propiedad asociativa: 1 + (2 + 3) = (1+ 2) + 3
  • Tiene un elemento neutro, el cero: 1 + 0 = 1

Con la multiplicación de enteros pasa lo mismo (cuyo elemento neutro es el 1), y con la concatenación de cadenas de texto también (siendo la cadena vacía “”, el elemento neutro). Genial, ya voy entendiéndolo.

Monoides en Scalaz

Para trabajar con monoides vamos a utilizar en este post, la librería Scalaz. Scalaz ofrece un sin fin de opciones, abstracciones y funcionalidades. Hoy solo vamos a dar unas pequeñas pinceladas que nos permitirán realizar un ejemplo. Prometemos en un futuro hablar detenidamente de esta librería 🙂

Lo primero que tenemos que hacer es importar Scalaz en nuestro proyecto. Para ello, incluimos la dependencia en nuestro proyecto:

libraryDependencies += "org.scalaz" %% "scalaz-core" % "7.1.3"

y realizamos los siguientes imports:

import scalaz._, Scalaz._

Si cotilleamos un poco por la librería, podemos encontrarnos con la clase Monoid, o la clase Semigroup. En ellas estará definida la funcionalidad de la que podremos aprovecharnos.

En la clase Monoid está definido el método zero, que servirá para indicar cual es el elemento neutro. Además, en el trait MonoidLaw, está definida la regla fundamental que tiene que cumplir un elemento neutro.

Pero, ¿dónde está la información asociada a la propiedad asociativa?. Pues está en la clase Semigroup. Parece ser que los monoides son una especialización de los semigrupos. Estos últimos, son los encargados de la regla asociativa, mientras que los monoides añaden la regla del elemento neutro. De hecho, si nos vamos a la clase Semigroup, podemos encontrar el método append, el cual representará nuestra operación binaria |+|, y un poco más abajo podemos ver como está definida la propiedad asociativa.

Lo siguiente que debemos saber es que Scalaz se basa en una estructura de type classes. Es por ello que tendremos que implementar implicit objects, definiendo el comportamiento propio de nuestras estructuras respecto a las propiedades monádicas.

Después de esta escasa pero suficiente (o eso espero) información, vamos a ver como podemos aplicar todo este conocimiento con un ejemplo sencillo.

Creando nuestro propio monoide

Ahora, para acabar, vamos a crear nuestro propio monoide con Scalaz. Vamos a comenzar creando unos nuevos tipos de datos. Vamos a crear dos tipos de átomos: oxígeno e hidrógeno.

trait Atom
case object Oxygen extends Atom
case object Hydrogen extends Atom

Y para terminar, el tipo molécula. Dicho tipo tendrá un número determinado de átomos (que pueden ser de distintos tipos), y una estabilidad molecular.

case class Molecule(atoms: Map[Atom, Int], stability: Double)

jesse-pinkman-yeah-bitch-science

Queremos trabajar con nuestro tipo molécula como si fuera un monoide, para ello, utilizando las type classes de Scalaz, vamos a definir un implicit object definiendo el comportamiento monádico de la molécula (esto parece un trabalenguas):

implicit object MoleculeIsMonoid extends Monoid[Molecule] {

  def zero: Molecule = Molecule(Map(), 1)
  def append(f1: Molecule, f2: => Molecule): Molecule = f1.fusion(f2)

}

Hemos decidido que la molécula neutra sea aquella que no tiene átomos, y que su estabilidad es 1, la máxima estabilidad posible.

Además, hemos decidido que la suma de moléculas se delegue en una función llamada fusion. Vamos a ver como está definida:

case class Molecule(atoms: Map[Atom, Int], stability: Double) {

def fusion(other: Molecule): Molecule =
  Molecule(
    atoms = atoms |+| other.atoms,
    stability = stability * other.stability
  )
}

Como podemos observar, la fusión de dos moléculas consiste en sumar los átomos que las forman como si fueran monoides. Y es que resulta, que Scalaz nos proporciona funcionalidad para tratar la combinación de mapas como monoides. Además, vamos a combinar las estabilidades como si fueran probabilidades, es decir, multiplicándolas.

El método append también se puede utilizar mediante el símbolo |+|, justo como lo habíamos definido en un primer momento.

¡Y ya está! Ahora puedo fusionar moléculas aprovechándome de que son monoides, y de las operaciones que nos ofrece Scalaz con dichas abstracciones:

val m1 = Molecule(Map(Oxygen -> 1, Hydrogen -> 2), 0.2)
val m2 = Molecule(Map(Oxygen -> 1), 0.3)

m1 |+| m2
//Molecule(Map(Oxygen -> 2, Hydrogen -> 2), 0.06)

List(m1, m2, m2, m1).foldLeft(Molecule(Map(), 1))(_ |+| _)
//Molecule(Map(Oxygen -> 4, Hydrogen -> 4), 0.0036)

Some(m1) |+| Some(m2)
//Some(Molecule(Map(Oxygen -> 2, Hydrogen -> 2), 0.06))

46196561

Conclusión

Cuando llegues aquí espero que hayas perdido un poco de miedo a las palabras raras. Como hemos visto, las abstracciones algebraicas, son simplemente una manera de poner nombre a elementos que cumplen una serie de propiedades. Después, podremos beneficiarnos de esta abstracción para poder tratar varias operaciones distintas de la misma forma. Esta vez le ha tocado el turno a los monoides. Más adelante llegarán otros conceptos que ya te deberían de sonar, como las famosas mónadas. Ahora ya sabes, a fardar delante de tus amigos hablando de monoides 🙂

Type classes in Scala

After a few months with the blog, we now know where we can use traits, what is the purpose of implicits, or what the hell are generic types. Today, we are going to use a little bit of everything in order to explain type classes.

In spite of their name, type classes are not related with the object-oriented programming paradigm, but with functional programming. By using this technique, we will be able to add ad hoc functionality to the types we want. This applies for both the primitive types of the language and the custom types that we may have created.

How are type classes implemented in Scala?

In order to illustrate the explanation, we are going to use a small example.
First, what we will do is to create a trait with the functionality that we want to add to the types. As we want it to be reusable by different types, the trait will be parameterized with the generic type T.

trait Addable[T] {
  def +(a: T, b: T): T
}

Now the trait is well-defined, let’s define implicit objects that inherit from it. We will have to create an object for each type that we want to have such functionality. Plus, we must implement in each object how the elements of that given type should be added.

object Addables {
  implicit object AddableLikeInt extends Addable[Int] {
    def +(a: Int, b: Int): Int = a + b
  }

  implicit object AddableLikeString extends Addable[String] {
    def +(a: String, b: String): String = a + b
  }

  implicit object AddableLikeBoolean extends Addable[Boolean] {
    def +(a: Boolean, b: Boolean): Boolean = a && b
  }
}

As can be seen, in this example we’re defining the method + for the Int, String and Boolean types. Now these types can make use of the addition functionality.

Mmmm… and how exactly can they be used?

We have already defined type classes for three primitive types. To give use to them, we will define a method called plus, which implements the addition of two elements of the same type.

def plus[T](a1: T, a2: T)(implicit ev: Addable[T]): T = ev.+(a1, a2)

In order to force the type of the elements that we want to add to be Addable, we will use in the method signature an implicit value of type Addable[T]. This way, the method will look for an object that extends the Addable[T] type in its scope,  where T is the type of the values we want to add. This implicit value is often referred to as evidence.
Well, let’s test it:

import Addables._  

plus(true, false) //false

As a reminder, let me mention that syntactic sugar can be applied and Context Bounds be used to mark these evidences:

def plus[T:Addable](a1: T, a2: T)

Awesome! But what happens if we want to add a type not previously defined? Then, we would get an error telling us that there isn’t any implicit object with that type in the scope:

plus(1.0, 2.0) // error: could not find implicit value for parameter ev: Addable[Double]

Fixing this is quite simple. We just have to create another implicit object with the desired type. In this case, with the Double type:

implicit object AddableLikeDouble extends Addable[Double] {
  def +(a: Double, b: Double): Double = a + b
}

plus(1.0, 2.0) //3.0

Now we can add two values of type Double. The same thing applies to types defined by ourselves:

case class Group(name: String, people: Set[String])

implicit object AddableLikeGroup extends Addable[Group] {
  def +(a: Group, b: Group): Group =
    Group(
      name = a.name + " & " + b.name,
      people = a.people | b.people
    )
}

Now Groups can be added too:

plus(Group("A", Set("Peter")), Group("B", Set("John, Chris"))) 
//Group("A & B", Set("Peter", "John", "Chris"))

Bonus

As we have seen, type classes allow us to extend functionality in a simple and elegant way. It can be applied to both primitives types, as well as our own user-defined types. In addition, by handling the scope of implicit objects, we have control over when we want a specific type to extend a given functionality.

This technique is perfect for us to explore more complex matters in depth in future posts. What we have seen today is the first step in understanding monoids, algebraic structures that can be found in functional libraries such as Scalaz or Cats. But this is all for now, I leave you yearning for more 🙂

Happy summer!

Type classes en Scala

Después de unas cuantos meses con el blog ya hemos podido saber donde podemos usar traits, para qué sirven los implícitos o qué narices son eso de los tipos genéricos. Hoy vamos a utilizar un poco de todo para explicar las type classes.

Las type classes, a pesar del nombre, no provienen del paradigma orientado a objetos, si no de la programación funcional. Utilizando esta técnica vamos a poder agregar funcionalidad de forma ad-hoc a los tipos que deseemos. Tanto tipos primitivos del lenguaje como tipos que hayamos creado nosotros mismos.

¿Cómo se construyen type classes en Scala?

Para ilustrar la explicación vamos a usar un pequeño ejemplo.
En primer lugar, lo que tenemos que hacer es crear un trait con la funcionalidad que queremos añadir a los tipos. Como queremos que sea posible utilizarlo para varios tipos distintos, el trait estará parametrizado con el tipo genérico T.

trait Addable[T] {
  def +(a: T, b: T): T
}

Ahora que ya tenemos el trait definido, vamos a definir objetos implícitos que hereden de dicho trait. Tendremos que crear un objeto por cada tipo que queramos que tenga dicha funcionalidad. Además, en cada objeto tendremos que implementar como se deben sumar los elementos de ese tipo específico.

object Addables {
  implicit object AddableLikeInt extends Addable[Int] {
    def +(a: Int, b: Int): Int = a + b
  }

  implicit object AddableLikeString extends Addable[String] {
    def +(a: String, b: String): String = a + b
  }

  implicit object AddableLikeBoolean extends Addable[Boolean] {
    def +(a: Boolean, b: Boolean): Boolean = a && b
  }
}

Como podemos ver, en este caso estamos definiendo el método + para los tipos Int, String y Boolean. Ahora ya podemos sumar estos tipos.

Mmmm…¿y cómo se usan?

Ya tenemos las type classes definidas para tres tipos primitivos. Para darles uso vamos a utilizar un método plus que sume dos elementos del mismo tipo.

def plus[T](a1: T, a2: T)(implicit ev: Addable[T]): T = ev.+(a1, a2)

Para obligar a que el tipo de los elementos que queremos sumar sea Addable vamos a utilizar en la signatura del método un valor implícito de tipo Addable[T]. De esta forma, el método buscara en el scope si existe un objeto que extienda del tipo Addable[T] cuyo T sea el tipo de los valores que queremos sumar. A este valor implícito se le suele denominar evidencia.
Ahora vamos a probarlo:

import Addables._  

plus(true, false) //false

Como recordatorio, comentar que podemos hacer uso de syntactic sugar y usar los Context Bound para marcar las evidencias:

def plus[T:Addable](a1: T, a2: T)

¡Genial! Pero, ¿qué ocurre si queremos sumar algún tipo que no habíamos definido anteriormente? Pues que nos va a dar un error indicando que no se encuentra en el scope un implícito con el tipo definido:

plus(1.0, 2.0) // error: could not find implicit value for parameter ev: Addable[Double]

Solucionarlo es muy fácil. Basta con crear otro implicit object con el tipo deseado. En este caso con el tipo Double:

implicit object AddableLikeDouble extends Addable[Double] {
  def +(a: Double, b: Double): Double = a + b
}

plus(1.0, 2.0) //3.0

Ahora ya podemos sumar dos valores de tipo Double. Y lo mismo ocurre para tipos definidos por nosotros mismos:

case class Group(name: String, people: Set[String])

implicit object AddableLikeGroup extends Addable[Group] {
  def +(a: Group, b: Group): Group =
    Group(
      name = a.name + " & " + b.name,
      people = a.people | b.people
    )
}

Ahora ya puedo sumar Groups:

plus(Group("A", Set("Peter")), Group("B", Set("John, Chris"))) 
//Group("A & B", Set("Peter", "John", "Chris"))

Bonus

Como hemos podido ver, las type class nos permiten extender funcionalidad de una forma muy sencilla y elegante. Nos sirve tanto para trabajar con tipos primitivos, como con tipos propios. Además, controlando el scope de los objetos implícitos, tenemos el control sobre cuando queremos que un tipo específico extienda una determinada funcionalidad.

Esta técnica nos viene perfecto para meternos en profundidad con materia más compleja en futuros post. Lo que hemos visto hoy es el primer paso para entender los monoids, una estructura algebraica que podemos encontrar en librerías funcionales como Scalaz o Cats. Pero por el momento os dejo con la miel en los labios 🙂

Feliz verano!