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: