Scalaz III: Apply y Applicative

En la primera entrada de la serie, Scalaz I: Functores y funciones como functores , realicé la descripción de functores y cómo utilizar funciones como functores. En la presente entrada, Scalaz III: Apply y Aplicative, me centraré en describir las API Apply y Applicative.

En Scalaz, Apply hereda de la Functor; y, Applicative, hereda a su vez de Apply.

Apply

El API Apply permite aplicar una función a un functor y, al estar definido mediante el patrón type classes, contiene un conjunto de funciones dentro de la sintaxis del type class.

La función principal es la función ap la cual define una función que es aplicada al functor. Así, una definición básica de Apply es la siguiente:

 trait Apply[F[_]] extends Functor[F] { self =>
   def ap[A,B](fa: => F[A])(f: => F[A => B]): F[B]
   [...]
 }

Analizando la función ap realizamos la siguiente lectura, dado un functor fa, se le aplica la función f; y,  en función del número de functores, podemos definir ap2, para dos functores, hasta ap8, para 8 functores.

Para poder operar con Apply, es necesario realizar las importaciones en el código de la siguiente manera:

import scala.language.higherKinds
import scalaz.Apply
import scalaz.Scalaz._

Ejemplo Apply con un functor

Dada una función con nombre incrementoMas2 la cual realiza el incremento en dos unidades de un entero, se aplica la función ap de Apply para realizar el incremento de un functor definido por un elemento Option de valores enteros de la siguiente forma:

 val incrementoMas2 = (x: Int) => x + 2
 println(s"[2] Apply[Option].ap(Some(3))(Some(incrementoMas2))=${Apply[Option].ap(Some(3))(Some(incrementoMas2))}")

La salida por consola es la siguiente:

 [2] Apply[Option].ap(Some(3))(Some(incrementoMas2))=Some(5)

Como he comentado antes, Scalaz contiene funciones de la sintaxis de Apply. Así, la función de la sintaxis de la función ap es el símbolo «<*>». El ejemplo anterior utilizando la sintaxis y utilizando una función que incrementa un entero en 3 unidades, es el siguiente:

 println(s"[3] 3.some <*> {(_: Int) + 3}.some=${3.some <*> {(_: Int) + 3}.some}")
 println(s"[3.1] 3.some <*> incrementoMas2.some=${3.some <*> incrementoMas2.some}")

La salida por consola es la siguiente:

 [3] 3.some <*> {(_: Int) + 3}.some=Some(6)
 [3.1] 3.some <*> incrementoMas2.some=Some(5)

Ejemplo Apply con dos functores

Dada una función con nombre sumaDosEntores la cual realiza la suma de dos enteros, se aplica la función ap2 de Apply para realizar la suma de dos functores definido por dos elementos Option de valores enteros de la siguiente forma:

 val sumaDosEnteros = (e1:Int, e2:Int) => e1 + e2
 val result2 = Apply[Option].ap2(Some(3), Some(4))(Some(sumaDosEnteros))
 println(s"[2] Apply[Option].ap2(Some(3), Some(4))(Some(sumaDosEnteros))=${result2}")

La salida por consola es la siguiente:

 [2] Apply[Option].ap2(Some(3), Some(4))(Some(sumaDosEnteros))=Some(7)

Aplicando la función «<*>» de la sintaxis de Apply, el ejemplo anterior, se define de la siguiente forma:

 println(s"[3.2] 3.some + 4.some <*> sumaDosEnteros.some=${ 3.some <*> {4.some <*> {sumaDosEnteros.curried.some}} }")

La salida por consola es la siguiente:

 [3.2] 3.some + 4.some <*> sumaDosEnteros.some=Some(7)

La sintaxis contiene otras funciones, como por ejemplo:

  •  función <*, dado dos functores se selecciona el functor de la izquierda
 println(s"1.some <* 2.some=${1.some <* 2.some}")
 println(s"none <* 2.some=${none <* 2.some}")

La salida por consola es la siguiente:

 1.some <* 2.some=Some(1)
 none <* 2.some=None
  • función *>, dado dos functores, se selecciona el functor de la derecha.
 println(s"1.some *> 2.some=${1.some *> 2.some}")
 println(s"1.some *> none=${1.some *> none}")

La salida por consola es la siguiente:

 1.some *> 2.some=Some(2)
 1.some *> none=None

Applicative

El API Applicative permite mapear una función con N parámetros. El API Applicative hereda del API Apply con lo cual el API opera con functores. Las funciones principales del API es la función point y su alias pure, las cuales permiten definir un functor a partir de un elemento de un determinado tipo. La definición de las funciones son las siguientes:

trait Applicative[F[_]] extends Apply[F] { self =>
 def point[A](a: => A): F[A]
 def pure[A](a: => A): F[A] = point(a)
 [...]
}

Ejemplos básicos Applicative

Dado un número entero, se aplica la función point del API Applicative para crear un functor Option y List de la siguiente forma:

 val result4 = Applicative[Option].point(3)
 println(s"[0] Applicative[Option].point(3)=${result4}")
 val result5 = Applicative[List].point(3)
 println(s"[0_1] Applicative[List].point(3)=${result5}")

La salida por consola es la siguiente:

[0] Applicative[Option].point(3)=Some(3)
[0_1] Applicative[List].point(3)=List(3)

Composición de funciones con Applicative

Dado una función que realiza el incremento de 10 unidades de un elemento entero, se define un elemento Applicative del tipo List de la siguiente forma:

 val result6 = Applicative[List].point(3).map( (elem:Int) => elem + 10 )
 println(s"[0_2] Applicative[List].point(3)=${result6}")

La salida por consola es la siguiente:

[0_2] Applicative[List].point(3)=List(13)

Función ap y apX en Applicative

  • Dada una función de incremento de un entero en dos unidades y un functor representado por un Option de enteros, se aplica la función ap del API Applicative de la siguiente forma:
 val inc: Int => Int = (x: Int) => x + 2
 val result1 = Applicative[Option].ap(Some(1))(Some(inc))
 println(s"[1] inc = (x: Int) => x + 2")
 println(s"[1] Applicative[Option].ap(Some(1))(Some(inc))=${result1}")

La salida por consola es la siguiente:

 [1] inc = (x: Int) => x + 2
 [1] Applicative[Option].ap(Some(1))(Some(inc))=Some(3)
  • Dada una función que transforma un entero en un functor del tipo Option; dada una función que realiza la operación de incremento de un elemento Option de enteros en dos unidades, se aplica la función ap del API Applicative de la siguiente forma:
 val inc: Int => Int = (x: Int) => x + 2
 val enteroASome: Int => Some[Int] = (x:Int) => Some(x)
 val optionDeFuncionDeEntero: Some[Int => Int] = Some(inc)
 val result1_1 = Applicative[Option].ap(enteroASome(1))( optionDeFuncionDeEntero )
 println(s"[1_1] Applicative[Option].ap(enteroASome(1))( optionDeFuncionDeEntero )=${result1_1}")

La salida por consola es la siguiente:

[1_1] Applicative[Option].ap(enteroASome(1))( optionDeFuncionDeEntero )=Some(3)
  • Dado un functor del tipo Option de enteros y ninguna función, el resultado de aplicar la función ap del API Applicative es el siguiente:
 val result1_2 = Applicative[Option].ap(Some(1))(None)
 println(s"[1_2] Applicative[Option].ap(Some(1))(None)=${result1_2}")

La salida por consola es la siguiente:

[1_2] Applicative[Option].ap(Some(1))(None)=None
  • Dada una función que realiza la suma de tres elementos enteros, se aplica la función ap3 del API Applicative de tres functores definidos en tres elementos Option de enteros de la siguiente forma:
 val sumaDe3 = (a: Int, b: Int, c: Int) => a + b + c
 val result2 = Applicative[Option].ap3(Some(1), Some(2), Some(3))(Some(sumaDe3))
 println(s"[1_3] Applicative[Option].ap3( Some(1), Some(2), Some(3) )(Some(sumaDe3))=${result2}")

La salida por consola es la siguiente:

[1_3] Applicative[Option].ap3( Some(1), Some(2), Some(3) )(Some(sumaDe3))=Some(6)
  • Dada una función que realiza la suma de tres elementos enteros, se aplica la función ap3 del API Applicative de tres functores definidos en tres elementos Option de enteros, uno de los cuales con valor None, de la forma siguiente:
 val sumaDe3 = (a: Int, b: Int, c: Int) => a + b + c
 val result2_1 = Applicative[Option].ap3(Some(1), None, Some(3))(Some(sumaDe3))
 println(s"[1_4] Applicative[Option].ap3( Some(1), None, Some(3) )(Some(sumaDe3))=${result2_1}")
 println

La salida por consola es la siguiente:

[1_4] Applicative[Option].ap3( Some(1), None, Some(3) )(Some(sumaDe3))=None
  • Dada una función que realiza la suma de tres elementos enteros, se aplica la función ap3 del API Applicative de tres functores definidos en tres elementos List de enteros de la siguiente forma:
 val sumaDe3 = (a: Int, b: Int, c: Int) => a + b + c
 val result3 = Applicative[List].ap3(List(1), List(2), List(3))(List(sumaDe3))
 println(s"[1_5] Applicative[List].ap3( List(1), List(2), List(3) )(List(sumaDe3))=${result3}")
 println

La salida por consola es la siguiente:

[1_5] Applicative[List].ap3( List(1), List(2), List(3) )(List(sumaDe3))=List(6)

Función Lift

  • Dada una función que realiza la suma de tres elementos de enteros, se define una función liftSumaDe3 a partir de la función de sumas de tres enteros para realizar la suma de tres elementos del tipo definido en el Applicative. El snippet es el siguiente:
 val sumaDe3 = (a: Int, b: Int, c: Int) => a + b + c
 val liftSumaDe3 = Applicative[Option].lift3( sumaDe3 )
 println(s"[1_6] liftSumaDe3( Some(2), Some(3), Some(5) )=${liftSumaDe3( Some(2), Some(3), Some(5) )}")
 println

La salida por consola es la siguiente:

[1_6] liftSumaDe3( Some(2), Some(3), Some(5) )=Some(10)

Función sequence

  • La función sequence realiza la conversión de tipos del elemento pasado por parámetro, es decir, dado un elemento P[G[_]], lo convierte en G[P[_]]. El snippet ejemplo es el siguiente:
 val resultApplicativeSequence = Applicative[Option].sequence(List(some(1), some(2), some(3)))
 println(s"[1_7] Applicative[Option].sequence(List(some(1), some(2), some(3)))=${Applicative[Option].sequence(List(some(1), some(2), some(3)))}")
 println

La salida por consola es la siguiente:

[1_7] Applicative[Option].sequence(List(some(1), some(2), some(3)))=Some(List(1, 2, 3))

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

Deja una respuesta

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. Salir /  Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Salir /  Cambiar )

Conectando a %s