Higher Kinded Types
y Type classes

Un poco de scala
Scala es un lenguaje que combina Programación Orientada a Objetos y Programación Funcional
def dobleLongitud(x: String): Int = {
2 * x.length
}
List("hola", "hi", "bonjour").map({ str =>
str.length * 2
})
Text

Un problema un poco abstracto
INtro
INtro
En listas tenemos la función map:
scala> List("hola", "hello", "bonjour").map( str => str.length )
res0: List[Int] = List(4, 5, 7)
En options también tenemos la función map:
scala> val x : Option[Int] = Some(7)
scala> x.map( n => n*2 )
Some(14)
scala> val x : Option[Int] = None
scala> x.map( n => n*2 )
None
En futures (similares a promesas en js) también tenemos la función map:
scala> val result: Future[String] = ??? // e.g. un servicio web
scala> val lines: Future[List[String]] = result.map( str => str.split("\n") )
INtro
List, Option y Future comparten la propiedad de ser "contenedores" y por eso tiene sentido la función map para transformar su contenido
Digamos que queremos implementar una función replace para reemplazar el valor de un contenedor por un valor constante
INtro
class List[A] {
def map[B](f: A => B): List[B] = ...
def replace[B](b: B): List[B] = {
map(a => b)
}
}
Implementándolo para List:
scala> List(1,2,3).replace( "A" )
res0: List[String] = List("A", "A", "A")
Funcionando en List:
INtro
trait Option[A] {
def map[B](f: A => B): Option[B] = ...
def replace[B](b: B): Option[B] = {
map(a => b)
}
}
Implementándolo para Option:
scala> val x: Option[Int] = Some(4)
scala> x.replace("A")
res0: Option[String] = Some("A")
scala> val y: Option[Int] = None
scala> y.replace("A")
res1: Option[String] = None
Funcionando en Option:
INtro
trait Future[A] {
def map[B](f: A => B): Future[B] = ...
def replace[B](b: B): Future[B] = {
map(a => b)
}
}
Implementándolo para Future:
scala> val result: Future[String] = ... // http request
scala> val replacedResult: Future[Int] = result.replace(100)
// Después de hacer la solicitud http reemplace
// el resultado por una constante
Funcionando en Future:
INtro
replace está implementado de forma idéntica en los tres casos, solo depende de map
Tenemos tres implementaciones de map, para tipos distintos:
def mapList [A,B](list : List[A] , f: A => B): List[B] = ...
def mapOption[A,B](option: Option[A], f: A => B): Option[B] = ...
def mapFuture[A,B](future: Future[A], f: A => B): Future[B] = ...
Generalizando para cualquier tipo contenedor F:
def map[A,B](fa: F[A], f: A => B): F[B]
...podríamos implementar replace de forma genérica para cualquier F:
def replace[A,B](fa: F[A], b: B): F[B] = map(fa, a => b)
INtro
¿Como podemos generalizar esto?
Requerimos de dos cosas:
- Una forma de decirle al sistema de tipos que estamos hablando de forma genérica de tipos "contenedores"
- Una forma de implementar la misma operación para tipos distintos, implementándola de forma diferente para cada tipo
Higher Kinded Types
Type Classes
Higher Kinded Types
Los tipos de los tipos

Los tipos clasifican valores:
scala> val x = 1
scala> val s = "asdfadfas"
scala> val f = { x: Int => x + "n" }
scala> val l = List(1,2,3)
Tipos
Los tipos clasifican valores:
scala> val x = 1
scala> val s = "asdfadfas"
scala> val f = { x: Int => x + "n" }
scala> val l = List(1,2,3)
scala> :t x
Int
Tipos
Los tipos clasifican valores:
scala> val x = 1
scala> val s = "asdfadfas"
scala> val f = { x: Int => x + "n" }
scala> val l = List(1,2,3)
scala> :t x
Int
scala> :t s
String
Tipos
Los tipos clasifican valores:
scala> val x = 1
scala> val s = "asdfadfas"
scala> val f = { x: Int => x + "n" }
scala> val l = List(1,2,3)
scala> :t x
Int
scala> :t s
String
scala> :t f
Int => String
Tipos
Los tipos clasifican valores:
scala> val x = 1
scala> val s = "asdfadfas"
scala> val f = { x: Int => x + "n" }
scala> val l = List(1,2,3)
scala> :t x
Int
scala> :t s
String
scala> :t f
Int => String
scala> :t l
List[Int]
Tipos
Los kinds clasifican tipos
¿Cual es el kind de Int?
Los tipos propios son aquellos tipos de los que podemos construir un valor.
:k -v Int
*
This is a proper type.
Int es un tipo autocontenido. A diferencia de otros tipos, como List no requiere especificar ningún tipo.
Kinds
scala> :k -v String
Kinds
scala> :k -v String
*
This is a proper type.
Kinds
Kinds
scala> :k -v String
*
This is a proper type.
scala> :k -v Int => String
scala> :k -v String
*
This is a proper type.
scala> :k -v Int => String
*
This is a proper type.
Kinds
scala> :k -v String
*
This is a proper type.
scala> :k -v Int => String
*
This is a proper type.
scala> :k -v List[Float]
Kinds
scala> :k -v String
*
This is a proper type.
scala> :k -v Int => String
*
This is a proper type.
scala> :k -v List[Float]
*
This is a proper type.
Kinds
scala> :k -v String
*
This is a proper type.
scala> :k -v Int => String
*
This is a proper type.
scala> :k -v List[Float]
*
This is a proper type.
scala> case class Foo(a: Int, b: String)
scala> :k -v Foo
Kinds
scala> :k -v String
*
This is a proper type.
scala> :k -v Int => String
*
This is a proper type.
scala> :k -v List[Float]
*
This is a proper type.
scala> case class Foo(a: Int, b: String)
scala> :k -v Foo
*
This is a proper type.
Kinds
scala> :k -v String
*
This is a proper type.
scala> :k -v Int => String
*
This is a proper type.
scala> :k -v List[Float]
*
This is a proper type.
scala> case class Foo(a: Int, b: String)
scala> :k -v Foo
*
This is a proper type.
* Nota: La consola de Scala de verdad se equivoca al preguntarle el kind en estos casos. Otros lenguajes con Higher Kinded Types si retornan la respuesta correcta en estos casos
Kinds
scala> :k -v String
*
This is a proper type.
scala> :k -v Int => String
*
This is a proper type.
scala> :k -v List[Float]
*
This is a proper type.
scala> case class Foo(a: Int, b: String)
scala> :k -v Foo
*
This is a proper type.
scala> :k -v List
Kinds
scala> :k -v String
*
This is a proper type.
scala> :k -v Int => String
*
This is a proper type.
scala> :k -v List[Float]
*
This is a proper type.
A diferencia de los anteriores no podemos construir un valor de tipo solo List
Debemos llenar el [_] en List[_] con algún tipo.
List es un constructor de tipos, es decir ¡un tipo de orden superior!
scala> case class Foo(a: Int, b: String)
scala> :k -v Foo
*
This is a proper type.
scala> :k -v List
Es decir List es un tipo que necesita a otro tipo para poder "especificarse" y así crear un tipo propio.
Kinds
Así como hay funciones de orden superior (funciones que reciben otras funciones, por ejemplo) existen tipos de orden superior (tipos que reciben otros tipos)
:k List
Higher Kinded Types
Así como hay funciones de orden superior (funciones que reciben otras funciones, por ejemplo) existen tipos de orden superior (tipos que reciben otros tipos)
:k List
* -> *
Higher Kinded Types
Así como hay funciones de orden superior (funciones que reciben otras funciones, por ejemplo) existen tipos de orden superior (tipos que reciben otros tipos)
:k List
* -> *
:k Option
Higher Kinded Types
Así como hay funciones de orden superior (funciones que reciben otras funciones, por ejemplo) existen tipos de orden superior (tipos que reciben otros tipos)
:k List
* -> *
:k Option
* -> *
Higher Kinded Types
Así como hay funciones de orden superior (funciones que reciben otras funciones, por ejemplo) existen tipos de orden superior (tipos que reciben otros tipos)
:k List
* -> *
:k Option
* -> *
:k Future
Higher Kinded Types
Así como hay funciones de orden superior (funciones que reciben otras funciones, por ejemplo) existen tipos de orden superior (tipos que reciben otros tipos)
:k List
* -> *
:k Option
* -> *
:k Future
* -> *
Higher Kinded Types
Así como hay funciones de orden superior (funciones que reciben otras funciones, por ejemplo) existen tipos de orden superior (tipos que reciben otros tipos)
:k List
* -> *
:k Option
* -> *
:k Future
* -> *
:k Either
Higher Kinded Types
Así como hay funciones de orden superior (funciones que reciben otras funciones, por ejemplo) existen tipos de orden superior (tipos que reciben otros tipos)
:k List
* -> *
:k Option
* -> *
:k Future
* -> *
:k Either
(*,*) -> *
Higher Kinded Types
Así como hay funciones de orden superior (funciones que reciben otras funciones, por ejemplo) existen tipos de orden superior (tipos que reciben otros tipos)
:k List
* -> *
:k Option
* -> *
:k Future
* -> *
:k Either
(*,*) -> *
Higher Kinded Types
* Nota: La consola de Scala no devuelve los kinds en esta misma notación. La idea acá es transmitir lo importante.
Higher Kinded Types
Estos tipos se denominan:
tipos abstractos o constructores de tipos o higher kinded types
Así como hay funciones de orden superior (funciones que reciben otras funciones, por ejemplo) existen tipos de orden superior (tipos que reciben otros tipos)
:k List
* -> *
:k Option
* -> *
:k Future
* -> *
:k Either
(*,*) -> *
Algunos paralelos
Higher Kinded Types
Algunos paralelos
def id(a: Int): Int = a
Higher Kinded Types
Algunos paralelos
def id(a: Int): Int = a
type Id[A] = A
Higher Kinded Types
Algunos paralelos
def id(a: Int): Int = a //:t id :: Int => Int
type Id[A] = A
Higher Kinded Types
Algunos paralelos
def id(a: Int): Int = a //:t id :: Int => Int
type Id[A] = A //:k Id :: * -> *
Higher Kinded Types
Algunos paralelos
def id(a: Int): Int = a //:t id :: Int => Int
type Id[A] = A //:k Id :: * -> *
def id(f: Int => Int, a: Int): Int = f(a)
Higher Kinded Types
Algunos paralelos
def id(a: Int): Int = a //:t id :: Int => Int
type Id[A] = A //:k Id :: * -> *
def id(f: Int => Int, a: Int): Int = f(a)
type Id[A[_],B] = A[B]
Higher Kinded Types
Algunos paralelos
def id(a: Int): Int = a //:t id :: Int => Int
type Id[A] = A //:k Id :: * -> *
def id(f: Int => Int, a: Int): Int = f(a) //:t id :: (Int => Int, Int) => Int
type Id[A[_],B] = A[B]
Higher Kinded Types
Algunos paralelos
def id(a: Int): Int = a //:t id :: Int => Int
type Id[A] = A //:k Id :: * -> *
def id(f: Int => Int, a: Int): Int = f(a) //:t id :: (Int => Int, Int) => Int
type Id[A[_],B] = A[B]
¿Qué quiere decir A[_]?
Higher Kinded Types
Algunos paralelos
def id(a: Int): Int = a //:t id :: Int => Int
type Id[A] = A //:k Id :: * -> *
def id(f: Int => Int, a: Int): Int = f(a) //:t id :: (Int => Int, Int) => Int
type Id[A[_],B] = A[B]
Un tipo de orden superior que recibe un solo tipo
:k A :: * -> *
¿Qué quiere decir A[_]?
Higher Kinded Types
Algunos paralelos
def id(a: Int): Int = a //:t id :: Int => Int
type Id[A] = A //:k Id :: * -> *
def id(f: Int => Int, a: Int): Int = f(a) //:t id :: (Int => Int, Int) => Int
type Id[A[_],B] = A[B] //:k Id :: ( * -> * , * ) -> *
Un tipo de orden superior que recibe un solo tipo
:k A :: * -> *
¿Qué quiere decir A[_]?
Higher Kinded Types

Higher Kinded Types
Ejemplo
¿Qué tipos coinciden con D?
Tipos propios como Int, String, List[Int] o Either[Throwable, MyClass]
¿Qué tipos coinciden con F[_]?
Tipos de orden superior que necesitan un (1) tipo como: Future, List, Option, o Vector
¿Qué tipos coinciden con G[_,_]?
Tipos de orden superior que necesitan dos (2) tipos como: Either, Map, o Function1
Higher Kinded Types
trait Example[ D , F[_] , G[_,_] ]
trait Example[ D , F[_] , G[_,_] ]
trait Impl0 extends Example[ Int , List , Either ]
trait Impl1 extends Example[ List[Int] , List , Validation]
trait Impl2 extends Example[ List , List , Either ]
trait Impl3 extends Example[ Int , Either , Either ]
trait Impl4 extends Example[ Int , List , Future ]

abstrayendo comportamientos
Type classes
Nada que ver con Clases de Programación Orientada a Objetos
No son una construcción del lenguaje. Mas bien son un patrón que utiliza distintas partes del lenguaje.
Motivación:
- Polimorfismo
- Desacoplamiento
Inspiradas en las Type Classes de Haskell
Type classes
Polimorfismo
La misma operación funcionando en distintos tipos
Type classes
Polimorfismo
Subtipado
Un mismo nombre para multiples instancias que estan relacionadas por una misma superclase.
La misma operación funcionando en distintos tipos
Type classes
Polimorfismo
Subtipado
La misma operación funcionando en distintos tipos
trait Persona {
def numeroIdentificacion: String
}
case class PersonaNatural extends Persona
case class PersonaJuridica extends Persona
Type classes
Polimorfismo
Polimorfismo Paramétrico
La misma operación funcionando en distintos tipos, pero ignorando la diferencia entre los tipos
Subtipado
La misma operación funcionando en distintos tipos
trait Persona {
def numeroIdentificacion: String
}
case class PersonaNatural extends Persona
case class PersonaJuridica extends Persona
Type classes
Polimorfismo
Polimorfismo Paramétrico
Subtipado
La misma operación funcionando en distintos tipos
trait Persona {
def numeroIdentificacion: String
}
case class PersonaNatural extends Persona
case class PersonaJuridica extends Persona
def invertirLista[A](xs: List[A]): List[A]
class Queue[A] {
...
}
Type classes
Polimorfismo
Polimorfismo Ad hoc
Polimorfismo Paramétrico
La misma operación funcionando en distintos tipos, haciendo algo distinto para cada tipo. Por ejemplo overloading
Subtipado
La misma operación funcionando en distintos tipos
trait Persona {
def numeroIdentificacion: String
}
case class PersonaNatural extends Persona
case class PersonaJuridica extends Persona
def invertirLista[A](xs: List[A]): List[A]
class Queue[A] {
...
}
Type classes
Polimorfismo
La misma operación funcionando en distintos tipos
Polimorfismo Paramétrico
Subtipado
trait Persona {
def numeroIdentificacion: String
}
case class PersonaNatural extends Persona
case class PersonaJuridica extends Persona
Type Classes
def invertirLista[A](xs: List[A]): List[A]
class Queue[A] {
...
}
Polimorfismo Ad hoc
Type classes
Consisten de:
- Un nombre
- Uno o mas parámetros de tipo (Cosas encerradas en [_])
- Un conjunto de declaraciones de funciones (en las que deben aparecer los tipos sea como parámetros o como tipos de retorno)
(Pueden haber funciones implementadas en función de otras sin implementar)
Type classes
trait Ord[T] {
def lessOrEq(a: T, b: T): Boolean
}
object intOrd extends Ord[Int] {
def lessOrEq(a: Int, b: Int): Boolean = a <= b
}
Un ejemplo:
Una instancia:
Si existe una instancia de la type class TC para un tipo T se suele decir "T está en la type class TC".
Por ejemplo, dado que definimos una instancia de Ord para Int podemos decir que Int está en la type class Ord
Type classes
Type classes
def sort[A](xs: List[A])(ordA: Ord[A]): List[A] = ...
Con esto podríamos abstraer ciertas cosas:
Type classes
def sort[A](xs: List[A])(ordA: Ord[A]): List[A] = ...
Con esto podríamos abstraer ciertas cosas:
La librería estándar tiene una type class similar para comparar elementos de un tipo y es la que se usa al llamar métodos como sorted: scala.math.Ordering
def sort[A](xs: List[A])(ordA: Ord[A]): List[A] = ...
sort(List(5, 8, 10, 9))(intOrd)
sort(List(4.3, 7.8, 1.9, 0.5))(doubleOrd)
sort(List("def" ,"zxy", "abc"))(stringOrd)
Con esto podríamos abstraer ciertas cosas:
Y podríamos utilizarla con tipos para los que hayamos definido una forma de comparar los elementos:
Type classes
def sort[A](xs: List[A])(ordA: Ord[A]): List[A] = ...
sort(List(5, 8, 10, 9))(intOrd)
sort(List(4.3, 7.8, 1.9, 0.5))(doubleOrd)
sort(List("def" ,"zxy", "abc"))(stringOrd)
Con esto podríamos abstraer ciertas cosas:
Pasar la instancia explícitamente a los métodos que lo necesitan puede ser cansón
Y podríamos utilizarla con tipos para los que hayamos definido una forma de comparar los elementos:
Type classes
def sort[A](xs: List[A])(ordA: Ord[A]): List[A] = ...
sort(List(5, 8, 10, 9))(intOrd)
sort(List(4.3, 7.8, 1.9, 0.5))(doubleOrd)
sort(List("def" ,"zxy", "abc"))(stringOrd)
Con esto podríamos abstraer ciertas cosas:
Pasar la instancia explícitamente a los métodos que lo necesitan puede ser cansón
¡Parámetros implícitos!
Y podríamos utilizarla con tipos para los que hayamos definido una forma de comparar los elementos:
Type classes
Scope
>
Entorno de variables definidas:
Scope
> val x = 1
x : Int
Entorno de variables definidas:
Scope
> val x = 1
> val y = "hola!"
x : Int
y : String
Entorno de variables definidas:
Scope
> val x = 1
> val y = "hola!"
> x + z
x : Int
y : String
Entorno de variables definidas:
Scope
> val x = 1
> val y = "hola!"
> x + z
> error: not found: value z
x : Int
y : String
Entorno de variables definidas:
Scope Implícito
> val x = 1
> val y = "hola!"
>
x : Int
y : String
Entorno de variables nombradas:
Entorno de variables implícitas:
Scope Implícito
> val x = 1
> val y = "hola!"
> implicit val w = "blabla"
x : Int
y : String
w : String
Entorno de variables nombradas:
w: String
Entorno de variables implícitas:
Scope implícito
def saludar(nombre: String)(implicit saludo: String): String = {
s"$saludo, $nombre"
}
> implicit val saludoEnEspañol = "Hola"
> saludar("Laura")
res0: String = Hola, Laura
Una función puede tener parámetros implícitos:
La función se puede invocar sin especificar los parámetros implícitos siempre que en el scope implícito de la invocación haya uno de ese tipo:
Scope implícito
> saludar("Laura")("Hi")
res0: String = Hi, Laura
Las funciones con parámetros implícitos se pueden llamar como cualquier otra función pasando explícitamente el valor de los parámetros implícitos:
Relación con Type classes
object Ord {
implicit object intOrd extends Ord[Int] {
def lessOrEq(a: Int, b: Int): Boolean = a <= b
}
implicit object stringOrd extends Ord[String] {
def lessOrEq(a: String, b: String): Boolean = {
(a compareTo b) <= 0
}
}
def sort[A](xs: Array[A])(implicit ord: Ord[A]): Array[A] = ...
}
Para facilitar el uso de type classes se pueden usar implicits:
import Ord._
sort(List(5, 8, 10, 9))
sort(List("def" ,"zxy", "abc"))
Equivalente al anterior. Notación que sirve para type classes de un solo parámetro.
object Ord {
implicit object intOrd extends Ord[Int] {
def lessOrEq(a: Int, b: Int): Boolean = a <= b
}
implicit object stringOrd extends Ord[String] {
def lessOrEq(a: String, b: String): Boolean = {
(a compareTo b) <= 0
}
}
def sort[A: Ord](xs: Array[A]): Array[A] = ...
}
Para facilitar el uso de type classes se pueden usar implicits:
import Ord._
sort(List(5, 8, 10, 9))
sort(List("def" ,"zxy", "abc"))
Relación con Type classes
Para facilitar el uso de type classes se pueden usar implicits:
import Ord._
sort(List(Persona(nombre = "Felipe"), Persona(nombre ="Andrea")))
Relación con Type classes
object Ord {
implicit object intOrd extends Ord[Int] {
def lessOrEq(a: Int, b: Int): Boolean = a <= b
}
implicit object stringOrd extends Ord[String] {
def lessOrEq(a: String, b: String): Boolean = {
(a compareTo b) <= 0
}
}
def sort[A](xs: Array[A])(implicit ord: Ord[A]): Array[A] = ...
}
Para facilitar el uso de type classes se pueden usar implicits:
import Ord._
sort(List(Persona(nombre = "Felipe"), Persona(nombre ="Andrea")))
error: could not find implicit value for parameter ord: Ord[Persona]
Relación con Type classes
object Ord {
implicit object intOrd extends Ord[Int] {
def lessOrEq(a: Int, b: Int): Boolean = a <= b
}
implicit object stringOrd extends Ord[String] {
def lessOrEq(a: String, b: String): Boolean = {
(a compareTo b) <= 0
}
}
def sort[A](xs: Array[A])(implicit ord: Ord[A]): Array[A] = ...
}
Scope implícito
Una forma de extraer valores del scope implícito:
def implicitly[T](implicit e: T): T = e
implicitly es una función, del preludio estándar, que recibe un tipo T y devuelve el valor implícito de valor T que se encuentre dentro del scope ímplicito de la llamada.
Scope implícito
scala> implicit val x = "Hola!"
x: String = Hola!
scala> val y = implicitly[String]
y: String = Hola!
scala> y == "Hola!"
res0: Boolean = true
scala> implicitly[Int]
<console>:0: error: could not find implicit value for parameter e: Int
implicitly[Int]
Ejemplo:
Si no se ha definido ninguno, falla con un error de compilación:
Scope implícito
scala> implicit val x = "Hola!"
x: String = Hola!
scala> implicit val y = "Chao!"
y: String = Chao!
scala> implicitly[String]
<console>:10: error: ambiguous implicit values:
both value x of type => String
and value y of type => String
match expected type String
implicitly[String]
^
Si en el scope actual se han definido 2 o mas valores implícitos del mismo tipo, (y ambos son igual de específicos) entonces falla con un error de compilación:
Scope implícito
Los implícitos se pueden propagar automáticamente entre métodos:
def compare[A](x: A, y: A)(implicit ord: Ord[A]): Boolean = {
ord.lessOrEq(x,y)
}
Un método que lo invoque debería proveer el argumento ord:
def min[A](x: A, y: A)(implicit ord: Ord[A]): Boolean = {
if(compare(x,y)) {
x
} else {
y
}
}
Scope implícito
Los implícitos se pueden propagar automáticamente entre métodos:
def compare[A](x: A, y: A)(implicit ord: Ord[A]): Boolean = {
ord.lessOrEq(x,y)
}
Un método que lo invoque debería proveer el argumento ord:
def min[A: Ord](x: A, y: A): Boolean = {
if(compare(x,y)) {
x
} else {
y
}
}
Scope implícito
Los implicitos se pueden "componer" automáticamente:
case class Foo(a: String, b: Int)
case class Bar(m: String)
Entorno de variables implícitas:
Scope implícito
Los implicitos se pueden "componer" automáticamente:
case class Foo(a: String, b: Int)
case class Bar(m: String)
implicit def toBar(implicit foo: Foo): Bar = {
Bar(s"${foo.a} -> ${foo.b}")
}
toBar: Foo => Bar
Entorno de variables implícitas:
Scope implícito
Los implicitos se pueden "componer" automáticamente:
case class Foo(a: String, b: Int)
case class Bar(m: String)
implicit def toBar(implicit foo: Foo): Bar = {
Bar(s"${foo.a} -> ${foo.b}")
}
toBar: Foo => Bar
Entorno de variables implícitas:
Funciones en el scope implícito se denominan conversiones implícitas
Scope implícito
Los implicitos se pueden "componer" automáticamente:
case class Foo(a: String, b: Int)
case class Bar(m: String)
implicit def toBar(implicit foo: Foo): Bar = {
Bar(s"${foo.a} -> ${foo.b}")
}
def printBar(implicit bar: Bar) = {
println(s"bar -> $bar")
}
toBar: Foo => Bar
Entorno de variables implícitas:
Scope implícito
Los implicitos se pueden "componer" automáticamente:
case class Foo(a: String, b: Int)
case class Bar(m: String)
implicit def toBar(implicit foo: Foo): Bar = {
Bar(s"${foo.a} -> ${foo.b}")
}
def printBar(implicit bar: Bar) = {
println(s"bar -> $bar")
}
scala> implicit val foo = Foo("hola", 1)
foo: Foo = Foo(hola,1)
toBar: Foo => Bar
foo: Foo
Entorno de variables implícitas:
Scope implícito
Los implicitos se pueden "componer" automáticamente:
case class Foo(a: String, b: Int)
case class Bar(m: String)
implicit def toBar(implicit foo: Foo): Bar = {
Bar(s"${foo.a} -> ${foo.b}")
}
def printBar(implicit bar: Bar) = {
println(s"bar -> $bar")
}
scala> implicit val foo = Foo("hola", 1)
foo: Foo = Foo(hola,1)
scala> printBar
toBar: Foo => Bar
foo: Foo
Entorno de variables implícitas:
Scope implícito
Los implicitos se pueden "componer" automáticamente:
case class Foo(a: String, b: Int)
case class Bar(m: String)
implicit def toBar(implicit foo: Foo): Bar = {
Bar(s"${foo.a} -> ${foo.b}")
}
def printBar(implicit bar: Bar) = {
println(s"bar -> $bar")
}
scala> implicit val foo = Foo("hola", 1)
foo: Foo = Foo(hola,1)
scala> printBar
bar -> Bar(hola -> 1)
toBar: Foo => Bar
foo: Foo
Entorno de variables implícitas:

Ejemplo
Ejemplo
/**
* Generic json value
*/
sealed trait JsValue { ... }
case object JsNull extends JsValue
case class JsBoolean(value: Boolean) extends JsValue
case class JsNumber(value: BigDecimal) extends JsValue
case class JsString(value: String) extends JsValue
case class JsArray(value: Seq[JsValue] = List()) extends JsValue {
...
}
case class JsObject(private val underlying: Map[String, JsValue]) extends JsValue {
...
}
Serialización JSON en Play-JSON (similar en Spray-JSON)
Un objeto JSON es representado en Scala con un ADT:
Una representación canónica de un valor JSON
trait JsonConvertible {
def toJsValue: JsValue
}
Una solución usando interfaces:
Ejemplo
trait JsonConvertible {
def toJsValue: JsValue
}
class Persona(name: String, age: Int) extends JsonConvertible {
def toJsValue = {
JsObject(Map(
"name" -> JsString( name ),
"age" -> JsNumber( age )
))
}
}
Una solución usando interfaces:
Ejemplo
trait JsonConvertible {
def toJsValue: JsValue
}
class Persona(name: String, age: Int) extends JsonConvertible {
def toJsValue = {
JsObject(Map(
"name" -> JsString( name ),
"age" -> JsNumber( age )
))
}
}
Una solución usando interfaces:
Problemas:
- Acoplamiento: Mala separación de responsabilidades
- ¿Cómo manejar múltiples formas de serializar un mismo objeto?
- ¿Cómo manejar clases externas que no controlamos? ¿Extenderlas e implementar la interface?
Ejemplo
Separar en una type class la responsabilidad de convertir a JsValue.
trait Writes[A] {
/**
* Convert the object into a JsValue
*/
def writes(o: A): JsValue
...
}
Una mejor solución
case class Ciudad(codigo: String, nombre: String)
Play JSON
object CiudadWrites extends Writes[Ciudad] {
def write(c: Ciudad): JsValue = {
JsObject(
"codigo" -> JsString( c.codigo ),
"nombre" -> JsString( c.nombre )
)
}
}
Es una mejora pero igual hay algo de trabajo manual.
Play JSON
Play Json, a partir de macros, puede derivar automáticamente el Writes de una clase, dado que en el scope implícito haya un Writes para cada uno de los tipos de atributos de la clase.
Play JSON
Dentro de Play existen Writes para los tipos básicos de Scala: String, Int, Double, Float, Boolean, etc..
Play Json, a partir de macros, puede derivar automáticamente el Writes de una clase, dado que en el scope implícito haya un Writes para cada uno de los tipos de atributos de la clase.
Play JSON
Dentro de Play existen Writes para los tipos básicos de Scala: String, Int, Double, Float, Boolean, etc..
Play Json, a partir de macros, puede derivar automáticamente el Writes de una clase, dado que en el scope implícito haya un Writes para cada uno de los tipos de atributos de la clase.
import play.api.libs.json._
Play JSON
Dentro de Play existen Writes para los tipos básicos de Scala: String, Int, Double, Float, Boolean, etc..
Play Json, a partir de macros, puede derivar automáticamente el Writes de una clase, dado que en el scope implícito haya un Writes para cada uno de los tipos de atributos de la clase.
Json.writes[MiCaseClass]
El mecanismo de macro se invoca llamando:
y devuelve un Writes[MiCaseClass]
case class Ciudad(codigo: String, nombre: String)
case class Persona(nombre: String, ciudadResidencia: Ciudad)
Play JSON
Dentro de Play existen Writes para los tipos básicos de Scala: String, Int, Double, Float, Boolean, etc..
Play Json, a partir de macros, puede derivar automáticamente el Writes de una clase, dado que en el scope implícito haya un Writes para cada uno de los tipos de atributos de la clase.
Json.writes[MiCaseClass]
El mecanismo de macro se invoca llamando:
y devuelve un Writes[MiCaseClass]
case class Ciudad(codigo: String, nombre: String)
case class Persona(nombre: String, ciudadResidencia: Ciudad)
Play JSON
case class Ciudad(codigo: String, nombre: String)
case class Persona(nombre: String, ciudadResidencia: Ciudad)
import play.api.libs.json.Json
scala> Json.writes[Persona]
Play JSON
case class Ciudad(codigo: String, nombre: String)
case class Persona(nombre: String, ciudadResidencia: Ciudad)
import play.api.libs.json.Json
scala> Json.writes[Persona]
<console>:13: error: No implicit writes for Ciudad available.
Json.writes[Persona]
Play JSON
case class Ciudad(codigo: String, nombre: String)
case class Persona(nombre: String, ciudadResidencia: Ciudad)
import play.api.libs.json.Json
scala> Json.writes[Persona]
<console>:13: error: No implicit writes for Ciudad available.
Json.writes[Persona]
scala> implicit val ciudadWrites = Json.writes[Ciudad]
Play JSON
case class Ciudad(codigo: String, nombre: String)
case class Persona(nombre: String, ciudadResidencia: Ciudad)
import play.api.libs.json.Json
scala> Json.writes[Persona]
<console>:13: error: No implicit writes for Ciudad available.
Json.writes[Persona]
scala> implicit val ciudadWrites = Json.writes[Ciudad]
scala> Json.writes[Persona]
res1: play.api.libs.json.OWrites$$anon$1@2113b9b1
Play JSON
case class Ciudad(codigo: String, nombre: String)
case class Persona(nombre: String, ciudades: Array[Ciudad])
Play JSON
Haciendo un cambio:
... sigue funcionando:
import play.api.libs.json.Json
scala> implicit val ciudadWrites = Json.writes[Ciudad]
scala> Json.writes[Persona]
res1: play.api.libs.json.OWrites$$anon$1@2113b9b1
Pero no fue necesario definir un Writes[Array[Ciudad]] explícitamente
Bastó con un Writes[Ciudad]
Writes[A] => Writes[Array[A]]
Una conversión implícita:
¿Por qué funcionó esto?
Derivando Type classes con implicits
implicit def arrWrites[A](implicit wrt: Writes[A]): Writes[Array[A]] = {
???
}
implicit def arrWrites[A](implicit wrt: Writes[A]): Writes[Array[A]] = {
new Writes[Array[A]] {
def writes(as: Array[A]): JsValue = ???
}
}
Derivando Type classes con implicits
¿Cuál es el subtipo de JsValue que corresponde a un arreglo de elementos?
implicit def arrWrites[A](implicit wrt: Writes[A]): Writes[Array[A]] = {
new Writes[Array[A]] {
def writes(as: Array[A]): JsValue = ???
}
}
Derivando Type classes con implicits
¿Cuál es el subtipo de JsValue que corresponde a un arreglo de elementos?
JsArray
implicit def arrWrites[A](implicit wrt: Writes[A]): Writes[Array[A]] = {
new Writes[Array[A]] {
def writes(as: Array[A]): JsValue = ???
}
}
Derivando Type classes con implicits
¿Cuál es el subtipo de JsValue que corresponde a un arreglo de elementos?
JsArray
implicit def arrWrites[A](implicit wrt: Writes[A]): Writes[Array[A]] = {
new Writes[Array[A]] {
def writes(as: Array[A]): JsValue = {
val jsvals: Seq[JsValue] = ???
JsArray( jsvals )
}
}
}
Derivando Type classes con implicits
Teniendo un Writes de A's podemos convertir en un JsValue cada elemento del arreglo de entrada:
implicit def arrWrites[A](implicit wrt: Writes[A]): Writes[Array[A]] = {
new Writes[Array[A]] {
def writes(as: Array[A]): JsValue = {
val jsvals: Seq[JsValue] = ???
JsArray( jsvals )
}
}
}
Derivando Type classes con implicits
implicit def arrWrites[A](implicit wrt: Writes[A]): Writes[Array[A]] = {
new Writes[Array[A]] {
def writes(as: Array[A]): JsValue = {
val jsvals: Seq[JsValue] = as.map(a => wrt.write(a)).toSeq
JsArray( jsvals )
}
}
}
Teniendo un Writes de A's podemos convertir en un JsValue cada elemento del arreglo de entrada:
Derivando Type classes con implicits
implicit def arrWrites[A](implicit wrt: Writes[A]): Writes[Array[A]] = {
new Writes[Array[A]] {
def writes(as: Array[A]): JsValue = {
val jsvals: Seq[JsValue] = as.map(a => wrt.write(a)).toSeq
JsArray( jsvals )
}
}
}
Teniendo un Writes de A's podemos convertir en un JsValue cada elemento del arreglo de entrada:
Derivando Type classes con implicits
type safety
Extensibilidad
Si no existe una instancia para el tipo no pasa el chequeo de tipos.
- Agregar nuevas implementaciones para el mismo tipo
- Agregar nuevos tipos con implementaciones del typeclass
Ventajas de type classes
- Extensión retroactiva: permiten simular la inclusión de nuevos métodos dentro de una clase sin modificar su código fuente
Composición
Ventajas de type classes
Posibilidad de derivar nuevas instancias a partir de existentes
Desacoplamiento
No requieren implementar las funcionalidades en el cuerpo de las clases
Promueven única responsabilidad
Bien diseñada una type class solo se ocupa de modelar un comportamiento muy específico
Type classes en librerias
Slick
Usar tipos ricos en Scala, que tienen su equivalente en la base de datos

Type classes en librerias
Slick
Usar tipos ricos en Scala, que tienen su equivalente en la base de datos

Type classes en librerias
Slick
Usar tipos ricos en Scala, que tienen su equivalente en la base de datos

Type classes en librerias
Play framework

Interpretar Query params
Type classes en librerias
Play framework
Interpretar Query params

Type classes en librerias
scalaz
Patrones de programación funcional

Demo
Patrón pimp my class

Agregando funcionalidades dónde no las hay
Una forma de "agregar" nuevos métodos a una clase que ya ha sido definida.
Puede ser útil para agregar métodos utilitarios a librerías externas
Aprovecha el mecanismo de conversiones implícitas
Patrón pimp my class
¿Cómo funciona el siguiente código?
import scala.concurrent.duration._
val d = 5.seconds
La clase scala.Int no tiene un método seconds
Patrón pimp my class
¡Una conversión implícita a una clase que sí lo tenga y que utilice el entero!
Patrón pimp my class
package object duration {
implicit class DurationInt(private val n: Int) {
...
def seconds = durationIn(SECONDS)
def second = seconds
...
}
}
...alternativamente:
package object duration {
implicit def toDurationInt(n: Int) = new DurationInt(n)
class DurationInt(private val n: Int) {
...
def seconds = durationIn(SECONDS)
def second = seconds
...
}
}
Con type classes
Recuperando el estilo de Programación Orientada a Objetos
trait Ord[T] {
def lessOrEq(a: T, b: T): Boolean
}
implicit object stringOrd extends Ord[String] {
def lessOrEq(a: String, b: String): Boolean = (a compareTo b) <= 0
}
stringOrd.lessOrEq("hola", "chao")
Los typeclasses promueven un estilo de invocación raro
Con type classes
Recuperando el estilo de Programación Orientada a Objetos
class OrdOps[T](a: T)(implicit ord: Ord[T]) {
def lessOrEq(b: T): Boolean = ord.lessOrEq(a,b)
}
Definir una clase de ayuda y una conversión implícita:
Con type classes
Recuperando el estilo de Programación Orientada a Objetos
class OrdOps[T](a: T)(implicit ord: Ord[T]) {
def lessOrEq(b: T): Boolean = ord.lessOrEq(a,b)
}
implicit def toOrdOps[T: Ord](t: T) = new OrdOps(t)
Definir una clase de ayuda y una conversión implícita:
Con type classes
Recuperando el estilo de Programación Orientada a Objetos
class OrdOps[T](a: T)(implicit ord: Ord[T]) {
def lessOrEq(b: T): Boolean = ord.lessOrEq(a,b)
}
implicit def toOrdOps[T: Ord](t: T) = new OrdOps(t)
"hola".lessOrEq("chao")
"hola" lessOrEq "chao"
Definir una clase de ayuda y una conversión implícita:
Con type classes
Mucho trabajo manual y repetitivo al definir typeclasses:

CONECTANDO Higher kinded TYPEs Y TYPE CLASSES

Juntando hilos
CONECTANDO Higher kinded TYPEs Y TYPE CLASSES
Podemos hacerlo mediante una type class:
trait Mappable[F[_]] {
def map[A,B](fa: F[A])(f: A => B): F[B]
def replace[A,B](fa: F[A], b: B): F[B] = {
map(fa, a => b)
}
}
def mapList [A,B](list : List[A] , f: A => B): List[B] = ...
def mapOption[A,B](option: Option[A], f: A => B): Option[B] = ...
def mapFuture[A,B](future: Future[A], f: A => B): Future[B] = ...
Volviendo al problema original queríamos generalizar esto:
CONECTANDO Higher kinded TYPEs Y TYPE CLASSES
Un Mappable es algo que tiene una operación map que ejecuta una función dentro del contexto de un "contenedor" F[_]
trait Mappable[F[_]] {
def map[A,B](fa: F[A])(f: A => B): F[B]
}
CONECTANDO TYPE LAMBDAS Y TYPE CLASSES
Una implementación de ejemplo:
object ListMappable extends Mappable[List] {
def map[A,B](la: List[A])(f: A => B): List[B] = {
la.map(f)
}
}
La implementación solo llama el método map definido dentro de la clase List
Fin

Fuentes y enlaces adicionales

Higher Kinded Types
Fuentes y enlaces adicionales
Type Classes e Implicits

Ilustraciones

“But I don’t want to go among mad people," Alice remarked.
"Oh, you can’t help that," said the Cat: "we’re all mad here. I’m mad. You’re mad."
"How do you know I’m mad?" said Alice.
"You must be," said the Cat, "or you wouldn’t have come here.”
― Lewis Carroll, Alice in Wonderland
(Versión Meetup) Higher Kinded Types y Type Classes
By Miguel Vilá
(Versión Meetup) Higher Kinded Types y Type Classes
- 1,586