Shapeless: Introducción y HLists (Parte 1)

Mis buenos amigos de Scalera me preguntaron si quería escribir un post para el blog, y me hicieron una oferta que no pude rechazar, “Te invitamos a una cerveza” dijeron… ¡y aquí estoy! No puedo decir que no a una buena cerveza un buen amigo. Este es el primer post que escribo relacionado con Scala, así que tened paciencia conmigo.

¿Qué es Shapeless?

Shapeless es una librería creada por Miles Sabin, cuyo propósito es mejorar la programación genérica en Scala.

post3_sp

Para conseguirlo utiliza una serie de macros y técnicas avanzadas a nivel del sistema de tipos, que muchos simplemente llaman whitchcraft (brujería), que llevan al lenguaje al límite. Hay muchos temas que tratar cuando hablamos de Shapeless, pero en esta primera parte nos vamos a centrar en los principios básicos, la estructura más popular… sí, estás en lo cierto, ¡estoy hablando de las HLists!

HLists

Esta es probablemente la característica más famosa de Shapeless, y el mejor punto de partida (en mi opinión) para empezar, así que comencemos introduciendo qué es una HList.

El nombre HList proviene de Heterogeneous List (listas heterogeneas), en otras palabras, una lista con elementos que pueden ser de distinto tipo. Eso es todo, el concepto es muy simple, pero las consecuencias son muy interesantes. Esto nos permite tener una lista formada por un String y un Int por ejemplo. Puede que estes pensando ahora mismo… Ya puedo hacer eso con una lista normal y corriente. Bueno, vamos a ver si es verdad:

scala> List("scalera", 3)
res0: List[Any] = List(scalera, 3)

Aunque pueda parecer que esta lista tiene dos elementos de distinto tipo, la verdad es que desde el punto de vista del sistema de tipos estos dos elementos son del mismo tipo, Any. No se puede hacer nada útil con esta lista, si quisieras hacerlo tendrías que hacer algún que otro casting, con los peligros que eso conlleva…

¿Cómo solucionan este problema las HList? Pues muy sencillo, una HList guarda el tipo de todos y cada uno de los elementos que la componen. De esta manera sabemos que tipo tiene el primer elemento, el segundo, etc. Vamos a probar con el ejemplo anterior, esta vez utilizando una HList.

La sintaxis para crear una HList es la misma que para crear una List, sólo cambiaremos el nombre del constructor:

scala> HList("scalera", 3)
res0: shapeless.::[String,shapeless.::[Int,shapeless.HNil]] = scalera :: 3 :: HNil

El tipo de retorno es un poco confuso, pero podemos utilizar notación infija para simplificarlo, el tipo de retorno quedaría String :: Int :: HNil. Como puedes ver, tenemos todos los tipos guardados, ¡No perdemos ninguna información! Es más, ¡Podemos saber estáticamente el tamaño de la lista!

Así pues, podemos decir que las HLists son más potentes con las Lists estandar de Scala, y aquí hay un ejemplo para probarlo:

val list: List[Any] = "John Doe" :: 26 :: Nil
val hlist: String :: Int :: HNil = "John Doe" :: 26 :: HNil
list.head.toUpperCase // error: value toUpperCase is not a member of Any
hlist.head.toUpperCase // success: "JOHN DOE"

Como habrás podido ver, se pueden utilizar distintas sintaxis para construir una HList, al igual que para construir Lists:

HList("scalera", 3) <===> "scalera" :: 3 :: HNil
List(1, 2, 3) <===> 1 :: 2 :: 3 :: Nil

Además, puedes comprobar que muchos de los metodos definidos para Lists se pueden utilizar también para HLists.

val hlist = 1 :: "two" :: 3 :: "four" :: HNil
hlist.head // 1
hlist.tail // "two" :: 3 :: "four" :: HNil
hlist take 2 // 1 :: "two" :: HNil

Hasta ahora hemos visto que HList tiene similaridades con List, siendo la primera más potente, pero a parte de List, hay otras estructuras en Scala que son más parecidas a HList. Estas estructuras son las tuplas y las case classes.

Estas tres estructuras (hlists, tuplas y case classes) tienen la misma forma, puden guardar la misma información, y se puede ir de una a la otra sin perdida de información. Veamos un ejemplo:

case class User(name: String, age: Int)
 
type UserTuple = (String, Int)
type UserHList = String :: Int :: HNil
 
val user: User = User("John Doe", 26)
val userTuple: UserTuple = ("John Doe", 26)
val userHList: UserHList = "John Doe" :: 26 :: HNil
 
def fromUserToUserHList(user: User): UserHList = ???
def fromUserHListToUser(userHList: UserHList): User = ???
 
//...

Sería bastante directo implementar estas funciones, y te animo a que lo intentes. Es un buen ejercicio para tomar contacto con las HLists.

¿Por qué querría usar HList entonces?

Si una HList es muy parecida a una case class o una tupla, ¿Por qué son importantes? ¿Qué puedes hacer con una HList que no puedas hacer con una case class o una tupla? Pues bien, la gran diferencia es que HList es una abstracción más potente, abstrae sobre la aridad de las tuplas por ejemplo. No puedes escribir una función sobre tuplas, por que Tuple2 y Tuple3 no están relacionadas, sin embargo, puedes crear una función sobre HList y ejecutarla con HLists de tamaño arbitrario. Aunque esto queda fuera del ámbito de este post.

Conclusiones

Hemos visto que HList es una abstracción muy potente que nos brinda las ventajas de las listas y las tuplas al mismo tiempo. Sin embargo, esto es solo la punta del iceberg, ya que su verdadero potencial sale a relucir cuando las combinamos con otra typeclass de Shapeless, Generic. Pero esto lo dejaremos para otro post…

Anuncios

4 thoughts on “Shapeless: Introducción y HLists (Parte 1)

  1. Hola,
    Muy interesante el articulo, no conocía la librería shapeless y la verdad que mirando un poco por encima la documentación parece muy interesante. El tipo Any en scala lo uso muy poco porqué es un poco tedioso, y tenia la necesita de algo como Shapeless.

    Pues, espero que te hayan pagado la cerveza 🙂

    Un saludo
    Giuseppe

    Me gusta

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s