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
- Identidad Izquierda
- Identidad Derecha
- 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,183