Scalaz II: type class básicos

En la preente entrada, Scalaz II: Básico, me centraré en las types classes de Scalaz con un funcionamiento básico; como son las type classes: Equal, Show y Enum.

Scalaz Equal

La type classes Equal define funciones para realizar comparaciones. Para utilizar el API Equal, hay que importar el API y la sintaxis de Scalaz de la siguiente forma:

 import scalaz.Equal
 import scalaz.Scalaz._

Tipos básicos

Unos ejemplos de comparaciones con tipos básicos son los siguientes:

 println(s"1 === 1=>${1 === 1}")
 println(s"1 === 'algo'=>${ 1 == "algo" }")
 println(s"1.some =/= 2.some=>${1.some =/= 2.some}")
 println(s"1 assert_=== 1=>${1 assert_=== 1}")

La salida por consola del snippet anterior es el siguiente:

1 === 1=>true
1 === 'algo'=>false
1.some =/= 2.some=>true
1 assert_=== 1=>()

Ejemplos con case class

Sea una case class para definir una entidad de negocio cualquiera y unas instancias de dicha case class definidas de la siguiente forma:

 case class LuzDeTrafico(nobre: String)
 val rojo = LuzDeTrafico("rojo")
 val amarillo = LuzDeTrafico("amarillo")
 val verde = LuzDeTrafico("verde")

Sea una referencia implícita que define una operación con el API Equal con la case class anterior:

implicit val TrafficLightEqual: Equal[LuzDeTrafico] = Equal.equal(_ == _)

Se puede definir operaciones de comparación de la siguiente forma:

 println(s"rojo === rojo?${rojo === rojo}")
 println(s"rojo === amarillo?${rojo === amarillo}")

La salida por consola del snippet anterior es el siguiente:

 rojo === rojo?true
 rojo === amarillo?false

Scalaz Show

La type classes Show permite definir estructuras de datos como String. Los siguientes ejemplos muestran las funciones del API Show:

 println(s"3.show=>${3.show}")
 println(s"3.shows=>${3.shows}")
 "Saludos a la consola".println
 println(s"Saludos a la consola con show=>${ "Saludos a la consola con show".show}")

La salida por consola del snippet anterior es el siguiente:

 3.show=>3
 3.shows=>3
 "Saludos a la consola"
 Saludos a la consola con show=>"Saludos a la consola con show"

Scalaz Enum

La type class Enum permite definir estructuras secuenciales ordenadas. Las estructuras que podemos definir son las siguientes: lista de enteros, Streams de enteros y lista de caracteres. Unos snippet de ejemplo son los siguientes:

  • Definición de un rango desde los caracteres ‘a’ hasta ‘e’
 val enum1 = 'a' to 'e'
 println(s"'a' to 'e'=>${enum1}")
 println("Impresión del rango de enum1")
 enum1.foreach(println(_))
 println

La salida por la consola es la siguiente:

 'a' to 'e'=>NumericRange a to e
 Impresión del rango de enum1
 a
 b
 c
 d
 e
  • Definición de una lista con los elementos desde el caracter ‘a’ hasta ‘e’ y una lista de enteros desde 1 hasta 20.
 val enum2 = 'a' |-> 'e'
 println(s"enum2=>${enum2}")
 val enum2_1 = 1 |-> 20
 println(s"enum2_1=>${enum2_1}")

La salida por la consola es la siguiente:

 enum2=>List(a, b, c, d, e)
 enum2_1=>List(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20)
  • Definición de un Stream de enteros con los elementos comprendidos desde el valor 3 hasta 5.
 val enum3 = 3 |=> 5
 println(s"emun3=>${enum3}")
 println(s"emun3=>${enum3.headOption}")

La salida por la consola es la siguiente:

 emun3=>scalaz.EphemeralStream$$anon$5@37e547da
 emun3=>Some(3)

Para el lector interesado, las entradas que he realizado sobre Scalaz son las siguientes:

Scalaz I: Functores y funciones como functores

Scalaz es una librería de programación funcional para el lenguaje Scala. Scalaz proporciona un conjunto de estructuras de datos para completar la biblioteca Scala; todas estas estructuras, están definidas sobre type classes y sus correspondientes instancias.

En la presente entrada, Scalaz I: Functores y funciones como functores, me centraré en los functores y en definir funciones como functores mediante los elementos de la librería Scalaz.

Definición de función curring

Las funciones curring es aquella técnica funcional nombrada en honor al matemática Haskell Curry el cual fue el creador del lenguaje funcional Haskell.

Una función curry es aquella función que define un conjunto de parámetros y, en las invocaciones a dicha función, pueden emplearse un número menor de parámetros. Así, podemos definir un conjunto de funciones a partir de una función base. Un ejemplo de función curry es la siguiente:

 def funcionBase(x:Int)(y:Int)= x + y
 val funcionCurry1 = funcionBase(2)(_)
 println(s"funcionCurry1(3)=${funcionCurry1(3)}")

La función base con técnica curring es la función funcionBase la cual define una lista de parámetros separados con paréntesis. A partir de la función base, definimos la función funcionCurry1 cuyo primer parámetro tiene el valor fijo 2 y, el segundo parámetro, un valor a determinar. Una vez definida la funcionCurry, se realiza la invocación de la función con el valor variable.

La salida por consola del ejemplo anterior es la siguiente:

 funcionCurry1(3)=5

La función funcionCurry1 tiene como primer parámetro el valor enteror 2 y, si quisiéramos, podríamos definir tantas funciones como valores enteros como primer parámetro.

Definición de Functor

La teoría de categorías es aquel estudio de las matemáticas que trata mediante axiomas estructuras abstractas como si fueran una, utilizando objetos y morfismos. Los objetos y los morfirmos forman una categoría. Así, una categoría es un conjunto de objetos y unos morfismos los cuales son las transformaciones entre dichos objetos.

Dada una categoría A, un functor es aquel morfismo que transforma la categoría A en otro categoría B. A nivel práctico, desde un punto de vista de un desarrollador, un functor se puede ver como aquella operación que transforma un elemento A en otro B y, a un bajo nivel en el lenguaje Scala, se corresponde con la función map.

Funciones como curry

En el apartado anterior, Definición de función curring, he definido una función de tipo curry y, en el presente apartado, describiré cómo se transforma una función como tipo curry. Para transformar una función como curry hay que aplicar la función curried.

En el siguiente ejemplo, se define una función curry con nombre lista1; se define una lista de enteros a la cual se aplica una función para realizar la multiplicación de los elementos por un número; y, una vez definida, se realiza la invocación de la función por el operador a multiplicar. Como se observa en la segunda línea del siguiente ejemplo, se aplica la función map con un valor entero fijo correspondiente con el segundo parámetro de la función de lista1.

 val lista1 = List(1, 2, 3, 4) map { (_: Int) * (_: Int) }.curried
 val resultadoLista1 = lista1.map {_ (9)}
 println(s"Resultado1=${resultadoLista1}")
 println

La salida por consola del ejemplo anterior es la siguiente:

 Resultado1=List(9, 18, 27, 36)

Funciones como functores

En el apartado anterior con nombre Definición de Functor, he realiza una descripción a alto nivel de un functor. En el presente apartado, describiré cómo definir functores con funciones.

Las importaciones de los elementos necesarios de Scalaz para los ejemplos siguientes, son los siguientes:

 import scala.language.higherKinds
 import scalaz.std.list._
 import scalaz.std.option._
 import scalaz.syntax.functor._
 import scalaz.{Functor, Monad}

La definición de un Functor con Scalaz de una lista de enteros y un Option de enteros es el siguiente:

 val list: List[Int] = Functor[List].map(List(1, 2, 3))(_ * 2)
 val option: Option[String] = Functor[Option].map(Some(123))(_.toString)
 println("List(1, 2, 3), [_ * 2] =>" + list)
 println("Some(123), _.toString =>" + option)

En el ejemplo anterior, defino un Functor de un tipo List y un segundo Functor de tipo Option. A estos dos functores, se aplican la función map para realizar la transformación, mediante una función, de una lista y de un Option. La función map del elemento scalaz.Functor tiene como parámetros los datos y la función que los modifica.

La salida por consola del ejemplo es la siguiente:

 List(1, 2, 3), [_ * 2] =>List(2, 4, 6)
 Some(123), _.toString =>Some(123)

En muchos casos, es necesario realizar la función de un tipo genérico sin conocer los datos; para ello, utilizamos la función lift. Así, como ejemplo, podemos definir un Functor para un Option de la siguiente manera:

 def lifted: (Option[Int]) => Option[Int] = Functor[Option].lift((x: Int) => x + 1)
 println("0.- lift((x: Int) => x + 1; Some(54) =>" + lifted(Some(54)))

La salida por consola es la siguiente:

 0.- lift((x: Int) => x + 1; Some(54) =>Some(55)

Si deseamos realizar alguna operación sobre el resultado, se puede aplicar la función map como sigue:

 println("0.1.- lift((x: Int) => x + 1; Some(54) =>" + lifted(Some(54)).map((x: Int) => x + 5))

La salida por consola es la siguiente:

 0.1.- lift((x: Int) => x + 1; Some(54) =>Some(60)

Los ejemplos descritos hasta el momento han partido de la definición de un elemento Functor; pero, si deseamos definir una función y utilizarla como un Functor o una Mónada, ¿cómo se realiza? Para ello, es necesario definir la función y emplear la función lift. Para el caso de una Mónada, un ejemplo es el siguiente:

 val func = ((x: Int) => x + 1) lift Monad[Option]
 println("1 ->" + func(Some(87)))

La salida por consola es la siguiente:

 1 ->Some(88)

Si deseamos aplicar mas transformaciones con la función map de la Mónada, se realiza de la siguiente forma:

 println("1.1 ->" + func(Some(87)).map((x: Int) => x + 1))
 println("1.2 ->" + func(Some(87)).map((x: Int) => x + 1).map((x: Int) => x + 10))

La salida por consola es la siguiente:

 1.1 ->Some(89)
 1.2 ->Some(99)

Si deseamos definir la transformación como una función, se puede realizar de la siguiente forma:

 def func2(elem: Int) = func(Some(elem)).map((x: Int) => x + 1).map((x: Int) => x + 10)
 println("1.3 ->" + func2(87))

La salida por consola es la siguiente:

 1.3 ->Some(99)

La definición de una función para realizar un morfismo de una lista de enteros en otra lista de enteros, se puede definir de la siguiente manera.

 def liftedListInt: (List[Int] => List[Int]) = Functor[List].lift((x: Int) => x * 5)
 println("2 ->" + liftedListInt(List(1, 2, 3)))
 println("2.1 ->" + liftedListInt(List(1, 2, 3)).map((x: Int) => x * 5))

La salida por consola es la siguiente:

 2 ->List(5, 10, 15)
 2.1 ->List(25, 50, 75)

Otras funciones

Scalaz es un conjunto de componentes de type classes y, el patrón type classes, permite la definición de sintaxis. En el presente apartado, se identifican un conjunto de funciones pertenecientes a la sintaxis de functores. La importación de los elementos necesarios para los ejemplos son las siguientes:

 import scalaz.Functor
 import scalaz.Scalaz._

La selección de las funciones que he realizado son las siguientes:

  • Para convertir todos los elementos de una estructura a un elemento determinado, se emplea la función >|. Un ejemplo es el siguiente:
 val listaX = List(1,2,3) >| "x"
 println(s"List(1,2,3) >| 'x'=${listaX}")

La salida por consola es la siguiente:

 List(1,2,3) >| 'x'=List(x, x, x)
  • Para convertir todos los elementos de una estructura a un elemento determinado, se emplea la función as. Un ejemplo es el siguiente:
 val listaAs = List(1,2,3) as "x"
 println(s"List(1,2,3) as 'x'=${listaAs}")

La salida por consola es la siguiente:

 List(1,2,3) as 'x'=List(x, x, x)
  • Para convertir un conjunto de entrada en un conjunto de tuplas con elementos duplicados, se emplea la función fpair. Un ejemplo es el siguiente:
 val listaFPair = List(1,2,3).fpair
 println(s"List(1,2,3).fpair=${listaFPair}")

La salida por consola es la siguiente:

 List(1,2,3).fpair=List((1,1), (2,2), (3,3))
  • Para convertir un conjunto de entrada en un conjunto de duplas cuyo elemento derecho sea uno determinada, se emplea la función strengthR. Un ejemplo es el siguiente:
 val listaStrengthR = List(1,2,3).strengthR("x")
 println(s"List(1,2,3).strengthR('x')=${listaStrengthR}")

La salida por consola es la siguiente:

 List(1,2,3).strengthR('x')=List((1,x), (2,x), (3,x))
  • Para convertir un conjunto de entrada en un conjunto de duplas cuyo elemento izquierdo sea uno determinada, se emplea la función strengthL. Un ejemplo es el siguiente:
 val listaStrengthL = List(1,2,3).strengthL("x")
 println(s"List(1,2,3).strengthL('x')=${listaStrengthL}")

La salida por consola es la siguiente:

 List(1,2,3).strengthL('x')=List((x,1), (x,2), (x,3))
  • Para convertir un conjunto de entrada en un conjunto de tuplas vacías, se emplea la función void. Un ejemplo es el siguiente:
 val listaVoid = List(1,2,3).void
 println(s"List(1,2,3).void=${listaVoid}")

La salida por consola es la siguiente:

 List(1,2,3).void=List((), (), ())

La definición de un Functor o una Mónada mediante el patrón funcional type classes es una operación que requiere, para el desarrollador principiante en la programación funcional, un entendimiento de conceptos complejos. Mediante la utilización de Scalaz utilizar Functores y definir funciones como functores es un proceso comprensible y fácil de entender.

Para el lector interesado, las entradas que he realizado sobre Scalaz son las siguientes: