Mónadas
para programadores

Mónadas

  • Trend por casi un año
  • Populares por Haskell y Scala
  • Muchos tutoriales en Internet

Mi cara al leerlos todos

¨Monads for dummies"

Mi cara después...

Muchos días después...

Así me sentí

Hice lo que cualquier hubiera hecho...

¡La maldición: todo el que aprende qué
es una Mónada se vuelve incapaz de explicar qué es!

¡Vengo a demostrar que la Maldición es cierta!

¡Pero al menos
van a ver código!

Mónadas

Mónadas: agregan indirección a llamados
a funciones

¿Qué puedo hacer con
un grado de indirección?

"All problems in computer science can be solved by another level of indirection" -- David Wheeler

Mónadas = Indirección + Composicionalidad

Operaciones

Una Mónada
arropa un valor

Mónada<T>

Mónada

  • Unit
  • Bind / Map

Unit: T  Mónada<T>

Bind: teniendo Mónada<T> y conociendo T  U, retorna Mónada<U>

Código

public interface Mónada<T> {

  // unit es un constructor o fábrica estática
  public <U> Mónada<U> map(Function<T, U> f);
}

Mónada Identidad

public class Identidad<T> implements Mónada<T> {

  private T valor;

  private Identidad(final T valor) {
    this.valor = valor;
  }

  public static <T> Identidad<T> of(T valor) {
    return new Identidad<>(valor);
  }

  public <U> Identidad<U> map(Function<T, U> f) {
    U nuevo = f.apply(valor);
    // No se puede escapar de la Mónada
    return Identidad.of(nuevo);
  }

  public T get() {
    return valor;
  }
}
Identidad.of("Monads are fun")
         .map(String::toUppercase)
         .map(s -> s.replace(" ", "-"))
         .get(); // MONADS-ARE-FUN
String s = "Monads are fun";
String s1 = s.toUppercase();
String s2 = s1.replace(" ", "-");
// MONADS-ARE-FUN

Idéntico a

Identidad.of("Monads are fun")
         .map(s -> s.replace(" ", ""))
         .map(String::length)
         .get(); // 12
String s = "Monads are fun";
String s1 = s.replace(" ", "");
int length = s1.length();
// 12

Idéntico a

¿Qué es flatMap?

¡Cuidado con map()!

¿Qué pasa si f
en realidad es
Function<T, Mónada<U>>?

public class Identidad<T> implements Mónada<T> {

  public <U> Identidad<U> map(Function<T, U> f) {
    U nuevo = f.apply(valor);
    // No se puede escapar la Mónada
    return Identidad.of(nuevo);
  }

  public <U> Identidad<U> flatMap(Function<T, Identidad<U>> f) {
     Identidad<U> nuevo = f.apply(valor);
     return nuevo; // no hay necesidad de envolver en Mónada
  }
}

¡Eso es lo mínimo
necesario para tener
una clase Monádica!

Leyes de las Mónadas

Leyes de las Mónadas

  1. Identidad Izquierda
  2. Identidad Derecha
  3. Transitividad

Identidad Izquierda

Si tengo un valor x y una función f, crear una Mónada para x y aplicar f no debería cambiar el resultado si aplico f a x directamente

// Identidad Izquierda
String s = "Monads are fun";
String s1 = s.toUppercase();
String s2 = Identidad.of(s)
                     .map(String::toUppercase)
                     .get();
assert s1.equals(s2);

Identidad Derecha

Si tengo x y hago una Mónada de x, si hago map de unit, aún tengo x

// Identidad Derecha
String s = "Monads are fun";
String s1 = Identidad.of(s)
                     .flatMap(Identidad::of)
                     .get();
assert s.equals(s1);

Transitividad

Si tengo una Mónada
y tengo funciones f y g,
el resultado no cambia si las llamo una a una o las aplico en composición

// Transitividad
String s = "Monads are fun";
String s1 = Identidad.of(s)
                     .map(s -> s.toUppercase()
                                .replace(" ", "-"))
                     .get();
String s2 = Identidad.of(s)
                     .map(String::toUppercase)
                     .map(s -> s.replace(" ", "-")
                     .get();
assert s1.equals(s2);

Identidad es una Mónada

Aún si una clase no cumple completamente las leyes, no significa que no sea útil

Optional

Optional representa
la posible ausencia
de un valor

Una posible implementación...

public final class Optional<T> {
  
  private static final Optional<?> EMPTY = new Optional<>();
  private final T value;
  
  private Optional(T value) {
    this.value = Objects.requireNonNull(value);
  }

  private Optional() {
    this.value = null;
  }

  public static<T> Optional<T> empty() {
    return (Optional<T>) EMPTY;
  }

  public static <T> Optional<T> of(T value) {
    return value == null ? empty() : new Optional(value);
  }
  // más código
}
public final class Optional<T> {
  // más código
  public boolean isPresent() {
    return value != null;
  }

  public <U> Optional<U> map(Function<T, U> f) {
    Objects.requireNonNull(f);
    if (!isPresent()) {
      return empty();
    } else {
      return Optional.of(f.apply(value));
    }
  }

  public <U> Optional<U> flatMap(Function<T, Optional<U>> f) {
    Objects.requireNonNull(mapper);
    if (!isPresent())
      return empty();
    else {
      return Objects.requireNonNull(f.apply(value));
    }
  } 
}
public final class Optional<T> {
  // más código
  public Optional<T> filter(Predicate<T> predicate) {
    Objects.requireNonNull(predicate);
    if (!isPresent()) {
      return this;
    } else {
      return predicate.test(value) ? this : empty();
    }
  }

  public T orElse(T other) {
    return isPresent() ? value : other;
  }

  public T orElseGet(Supplier<T> other) {
    return isPresent() ? value : other.get();
  }
}

Optional aprovecha la indirección para evitar excepciones por mal manejo de null's

Q&A

¡Gracias!

@gaijinco

Mónadas para programadores

By Carlos Obregón

Mónadas para programadores

  • 2,112