Repasando Artículos: Monads For Functional Programming

gustavo94

Gustavo Preciado

Posted on November 30, 2018

Repasando Artículos: Monads For Functional Programming

Wadler, Philip , 2001

La programación funcional esta muy atada a las matemáticas y la definición de las funciones no es el único caso, otro ejemplo de esta relación es el concepto de Monada que se viene de la teoría de categorías en las matemáticas abstractas. Este concepto se integra en la programación para resolver los problemas que tenían los lenguajes funcionales puros al tratar de hacer algunas cosas que en los lenguajes imperativos son relativamente simples como manejar excepciones, llevar contadores o crear trazas de ejecución.

En el artículo mencionan como estos problemas surgen debido al flujo de datos explicito que se tiene en los lenguajes funcionales: "Pure functional languages have this advantage: all flow of data is made explicit. And this disadvantage: sometimes it is painfully explicit.", no obstante el articulo también resalta la modularidad que ofrece este flujo explicito de datos lo que ayuda a dar importancia al concepto de Monada.

Una monada, también conocida como construcción estándar, es en términos simples una estructura de datos que nos permite enriquecer el retorno de una función y a su vez facilita la composición de las mismas encapsulando algunos comportamientos que en los lenguajes imperativos se manejan como efectos colaterales. Un ejemplo clave para entender las monadas puede ser la estructura Try en Scala o en VAVR(librería funcional para JAVA) la cual permite almacenar excepciones, de forma que un objeto Try puede almacenar un Integer o una excepción lo que permite establecer el flujo de datos a través de la composición de funciones sin que se pierda el control de excepciones y sin que estas tengan que lidiar con todos los errores que pueden ocurrir en otras funciones que retornen un Integer.

El siguiente es un ejemplo de como usando la clase Try de VAVR se puede capturar una excepción durante la composición de funciones:


public static Try<Integer> dividir(Integer dividendo, Integer divisor) {
      return Try.of(() -> dividendo/divisor);
  }

  public static void main(String[] args) {
      Try<Integer> resultado = dividir(100, 0).flatMap(r1 -> dividir(r1, 10));
      System.out.println("RESULTADO 1 = " + resultado.map(String::valueOf).getOrElseGet(Throwable::getMessage));// RESULTADO 1 = / by zero

      resultado = dividir(100, 10).flatMap(r1 -> dividir(r1, 10));
      System.out.println("RESULTADO 2 = " + resultado.map(String::valueOf).getOrElseGet(Throwable::getMessage));// RESULTADO 2 = 1

  }

Leyes Monádicas

En la definición formal de monada se encuentran 3 leyes que se deben cumplir en estas estructuras, aunque pueden tener mayor valor teórico que practico.

"Left Unit" o "Left Identity"

no encontré una traducción al español

Esta ley indica que aplicar una función que retorna una Monada a un valor cualquiera debe ser igual a aplicar dicha función al valor que hay dentro de una Monada usando funciones de composición flatMap.


  public static void main(String[] args) {
      Integer cien = 100;
      Try<Integer> tryCien = Try.of(() -> cien);
      Boolean r = tryCien.flatMap(valor -> dividir(100, valor)   ).equals(  dividir(100, cien));// True
  }
  return a >>= f = f

"Right Unit" o "Right Identity"

no encontré una traducción al español

Similar a la anterior para cumplir esta ley si tenemos una función que reciba un valor y retorna este mismo pero dentro de una monada debemos poder aplicar esta función dentro de un flatMap o directamente al valor y obtener el mismo resultado.


  public static void main(String[] args) {
      Integer cien = 100;
      Try<Integer> tryCien = Try.of(() -> cien);
      // El método success retorna el valor en un try sin hacerle ningún cambio
      Boolean r = tryCien.flatMap(valor -> Try.success(valor)).equals(  Try.success(cien));// True
  }
  m >>= return = m

Los anteriores ejemplos en JAVA dan la falsa impresión que las dos leyes son la misma siendo "Right Identity" solo una especificidad de la anterior, pero como dije estas leyes son mas de valor teórico y su diferencia se hace mas evidente en lenguajes mas matemáticos como haskell.

Asociatividad o "Associativity"

En esta ley se hace referencia a la capacidad de las monadas de aplicar los métodos flatMap en secuencia o encerrando uno dentro de otro.


public class PruebaMonadas {

  public static Try<Integer> dividirPor10(Integer dividendo) {
      return Try.of(() -> dividendo/10);
  }

  public static Try<Integer> duplicar(Integer n1) {
      return Try.of(() -> n1 * 2);
  }

  public static void main(String[] args) {

      Try<Integer> tryCien = Try.of(() -> 100);
      Try<Integer> r1 = tryCien.flatMap(PruebaMonadas::dividirPor10).flatMap(PruebaMonadas::duplicar);
      Try<Integer> r2 = tryCien.flatMap(cien -> dividirPor10(cien).flatMap(PruebaMonadas::duplicar));
      // El método success retorna el valor en un try sin hacerle ningún cambio
      Boolean r = r1.equals(r2);// True

  }

}
  (m >>= f) >>= g  m >>= (\x -> f x >>= g)
💖 💪 🙅 🚩
gustavo94
Gustavo Preciado

Posted on November 30, 2018

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related