Java Fundamentalks:

Programación Funcional

Lo más importante que van a
aprender hoy

No es OOP vs FP

Es OOP + FP

Usar FP no
implica odiar la OOP

FP no
reemplaza
a la OOP

Hay mucho que aprender de la FP

y seguir amando la OOP

Una historia
de dos estilos

Distancia de 2 puntos

http://www.matematicatuya.com/GRAFICAecuaciones/ImS1a1.png

Estilo OOP "usual"

public class Punto {

  private int x;
  private int y;

  public Punto(int x, int y) {
    this.x = x;
    this.y = y;
  }

  public int getX() {
    return x;
  }

  public int getY() {
    return y;
  }

  public double distancia(Punto otro) {
    var diferenciaX = otro.x - x;
    diferenciaX *= diferenciaX;

    var diferenciaY = otro.y - y;
    diferenciaY *= diferenciaY;

    diferenciaX += diferenciaY;
    return Math.sqrt(diferenciaX);
  }
}

Posible estilo FP

public final class Punto {

  public final int x;
  public final int y;

  public Punto(int x, int y) {
    this.x = x;
    this.y = y;
  }

}
public static int suma(int a, int b) {
  return a + b;
}
	
public static int diferencia(int a, int b) {
  return a - b;
}
	
public static int cuadrado(int n) {
  return n * n;
}

public static double distancia(Punto p1, Punto p2) {
  return Math.sqrt(suma(cuadrado(diferencia(p2.x, p1.x)), cuadrado(diferencia(p2.y, p1.y))));
}

Otra opción FP

public static double cuadrado(Punto p1, Punto p2) {
  return ((BiFunction<Punto,Punto,Integer>) Matemáticas::cuadradoDistancia)
           .andThen(Math::sqrt)
           .apply(p1, p2);
}

public static int cuadradoDistancia(Punto p1, Punto p2) {
  return Stream.of(diferencia(p2.x, p1.x), diferencia(p2.y, p1.y))
			   .map(Mathematics::cuadrado)
			   .reduce(0, Mathematics::suma);
}

OOP

  • Las Clases son muy importantes
  • El encapsulamiento es muy importante
  • Los Side-Effects son parte del diseño
  • Las funciones son verbos
  • Las funciones, en general, se escriben
    con énfasis en cómo se hacen las cosas
  • Las funciones suelen ser largas
  • La mutación es aceptable y usualmente
    abusada en el nombre del desempeño
  • Las variables y objetos tienen estado

FP

  • Las Funciones son muy importantes
  • El encapsulamiento no es muy importante
  • Las funciones son puras, no hay Side-Effects
  • Las funciones son sustantivos
  • Las funciones, en general, se escriben
    con énfasis en qué hacen, si se usa
    pipelines mejor
  • Las funciones son muy cortas,
    ¡una sola línea está bien!
  • La mutación es inaceptable
  • Tener estado es indeseable, p.j. sin
    usar el operador de asignación

¿Qué es la FP?

FP

In computer science, functional programming is a programming paradigm where programs are constructed by applying and composing functions.

It is a declarative programming paradigm in which function definitions are trees of expressions that each return a value, rather than a sequence of imperative statements which change the state of the program.

Fuente: Wikipedia

¿Qué podemos aprender de la FP?

Favorece la inmutabilidad

public final class Punto {
  private final int x;
  private final int y;
  
  public Punto(int x, int y) {
    this.x = x;
    this.y = y;
  }
  
  public int x() {
    return this.x;
  }
  
  public int y() {
    return this.y;
  }
}

Inmutabilidad

  • Si una clase no va a ser extendida, debe ser final
  • Los atributos de una clase suelen ser final
  • NO debe tener setters o mutators
public final class Parámetros {

  private final Set<String> ciudades;
  private final Set<String> tipos;

  public Parámetros(HttpServletRequest request) {
    this.ciudades = Set.of(Requests.getParameterValues(request, "ciudad"));
    this.tipos = Set.of(Requests.getParameterValues(request, "tipo"));
  }
  
  public Set<String> ciudades() {
    return Collections.unmodifiableSet(this.ciudades);
  }

  public Set<String> tipos() {
    return Collections.unmodifiableSet(this.tipos);
  }
}

Immutabilidad

  • Si recibes un parámetro mutable, haz una copia
  • Si vas a retornar un atributo modificable,
    haz una copia o un equivalente
public final class Fracción {
  // código
  
  public Fracción suma(Fracción otra) {
    // código
  }

  public Fracción diferencia(Fracción otra) {
    // código
  }

  public Fracción inverso(Fracción otra) {
    // código
  }
  
  // más código
}
public class Artículo {

  private String título;
  private String descripción;
  private LocalDate fechaPublicación;
  
  // constructor
  
  public Artículo withTítulo(String título) {
    return new Artículo(título, this.descripción, this.fechaPublicación);
  }
  
  public Artículo withDescripción(String descripción) {
    return new Artículo(this.título, descripción, this.fechaPublicación);
  }
  
  public Artículo withFechaPublicación(LocalDate fechaPublicación) {
    return new Artículo(this.título, descripción, this.fechaPublicación);
  }
}

Inmutabilidad

  • Para operaciones que cambian el estado de un objeto, crea un nuevo objeto con el estado resultante
  • Si realmente necesitas modificar un atributo del objeto, en vez de setters, usa withers

¿Y qué sucede
con el performance?

La JVM está altamente optimizada para recoger
memoria de objetos con un corto
ciclo de vida

En caso de duda, utiliza un profiler

Hablamos de inmutabilidad en las clases, pero también tengamos en cuenta:

var diferenciaX = p2.x() - p1.x();
diferenciaX *= diferenciaX;

var diferenciaY = p2.y() - p1.y();
diferenciaY *= diferenciaY;

diferenciaX += diferenciaY;

return Math.sqrt(diferenciaX);

🤢

var diferenciaX = p2.x() - p1.x();
var cuadradoDiferenciaX = diferenciaX * diferenciaX;

var diferenciaY = p2.y() - p1.y();
var cuadradoDiferenciaY = diferenciaY * diferenciaY;

var sumaCuadradosDiferencias = cuadradoDiferenciaX + cuadradoDiferenciaY;

var distancia = Math.sqrt(sumaCuadradosDiferencias);
return distancia;

😀

Inmutabilidad

  • Evita reutilizar variables (incluyendo parámetros)
    Crea una nueva variable, su nombre te servirá como documentación del algoritmo que estás implementando

Favorece las
funciones pequeñas

Favorece dividir
una función en pequeñas funciones

public static boolean esBisiesto(int año) {
  return año % 4 == 0
      && (año % 400 == 0) || año % 100 != 0);
}

🤢

public static boolean esMúltiplo(int a, int b) {
  return a % b == 0;
}

public static boolean esBisiesto(int año) {
  return esMúltiplo(año, 4) 
      && (esMúltiplo(año, 400) || !esMúltiplo(año, 100));
}

😀

¿Qué es una
función pura?

Función Pura

  • Una función que solo depende de sus parámetros para producir su valor de retorno y no tiene side-effects
  • Si llamo a una función con los mismos parámetros, siempre debería obtener el mismo valor de retorno
  • Modela lo que es una función matemática

Las funciones puras hacen nuestro
código más sencillo de razonar

Funciones Puras
en OOP

¡Si un objeto es
inmutable, sus funciones pueden ser puras!

¿Y los Side-Effects?

No se eliminan,
pero se limitan

Funciones Declarativas

Funciones Declarativas

Entre más funciones uses
en tu algoritmo, más incrementa la posibilidad de que sea declarativa

¡Es posible
escribir código declarativo en OOP!

suscriptores.stream()
    .filter(Suscriptor::esPremium)
    .filter(suscriptor -> suscriptor.getDeuda() >= 0.0)
    .map(Suscriptor::getDeuda)
    .sorted()
    .limit(50)
    .reduce((a, b) -> a + b, 0.0);

Criba (Wikipedia)

public final class Criba {

  public Criba(int máximoPrimo) {
    this.máximoPrimo = máximoPrimo;
    inicializarCriba();
    tacharPrimosPorDefinición();
    tacharMúltiplosDePrimosConocidos();
  }

  // más código
}
private void tacharMúltiplosDePrimosConocidos() {
  IntStream.iterate(2, this::siguienteEntero)
    .takeWhile(n -> menorMúltiploPosiblementeNoTachado(n) < máximoPrimo)
    .filter(this::esPrimo)
    .forEach(this::tacharMúltiplos);
}

private void tacharMúltiplos(int n) {
  IntStream.iterate(menorMúltiploPosiblementeNoTachado(n), (múltiplo) -> siguienteMúltiplo(múltiplo, n))
    .takeWhile(múltiplo -> múltiplo < máximoPrimo)
    .forEach(this::tachar);
}
private void inicializarCriba() {
  crearCriba(máximoPrimo);
  marcarTodosLosNúmerosComoPrimos();
}

private void tacharPrimosPorDefinición() {
  tachar(0);
  tachar(1);
}

private boolean aúnQuedanMúltiplosMenoresQueMayorPrimo(int n) {
  return siguienteMúltiploPosiblementeNoTachado(n) < máximoPrimo;
}
private int siguienteEntero(int n) {
  return n + 1;
}
	
private int siguienteMúltiploPosiblementeNoTachado(int n) {
  return n * n;
}

private int siguienteMúltiplo(int anteriorMúltiplo, int divisor) {
  return anteriorMúltiplo + divisor;
}
private void tachar(int n) {
  this.criba[n] = false;
}

private void marcarTodosLosNúmerosComoPrimos() {
  Arrays.fill(criba, true);
}

private void crearCriba(int primoMáximo) {
  this.criba = new boolean[primoMáximo];
}

public boolean esPrimo(int n) {
  return this.criba[n];
}

Lecciones de la FP

  • Minimizar mutabilidad
  • Escribir muchas funciones
  • Preferir funciones puras
  • Limitar Side-Effects
  • Buscar funciones declarativas

¿Qué sigue siendo relevante de la OOP?

Clases y
Tipos de Datos

Encapsulamiento y protección de Invariantes

public final class Fracción {

  private final int denominador;
  private final int numerador;
  
  public Fracción(int denominador, inr numerador) {
    if (numerador == 0) {
      throw new IllegalArgumentException("Numerador 0");
    }
    
    this.denominador = denominador;
    this.numerador = numerador;
  }
  
  // más código
}
public Usuario buscarUsuario(String query) {
  // más codigo
}

public Usuario buscarUsuario(Query query) {
  // más código
}

¿Cuál es mejor?

Herencia también
es muy útil...

...usada con cuidado

public interface Link {

  String url();
  String text();
  String target();

  static Link of(String url, String text) {
    if (url.startsWith("http")) {
      return new ExternalLink(url, text);
    }

    if (url.contains("@")) {
      return new EmailLink(url, text);
    }

    return new SimpleLink(url, text);
  }
}
public class SimpleLink implements Link {

  private final String url;
  private final String text;
  private final String target;

  protected SimpleLink(String url, String text, String target) {
    this.url = Objects.requireNonNull(url);
    this.text = Objects.requireNonNull(text);
    this.target = Objects.requireNonNull(target);
  }

  SimpleLink(String url, String text) {
    this(url, text, "");
  }

  @Override
  public String url() {
    return url;
  }

  @Override
  public String text() {
    return text;
  }

  @Override
  public String target() {
    return target;
  }
}
public class ExternalLink extends SimpleLink {

  ExternalLink(String url, String text) {
    super(url, text, "_blank");
  }
}
public class EmailLink extends ExternalLink {

  EmailLink(String email, String text) {
    super(String.format("mailto:%s", email), text);
  }
}

Aprende sobre principios SOLID

y sobre
Patrones de Diseño

La FP no hace a los Patrones de Diseño obsoletos

Conclusión

Hacer software es difícil

y a veces es crítico

necesitamos de
todas las herramientas

Aprendamos todo lo que podamos de FP

Aprendamos todo lo que podamos de OOP

Los paradigmas no son malos,
pero pueden ser mal usados

Y sobre todo, seamos siempre
una comunidad incluyente

Q&A

Otros Recursos

¡Gracias!

@gaijinco

Java Fundamentalks: Programación Funcional

By Carlos Obregón

Java Fundamentalks: Programación Funcional

¿Qué es la FP y qué podemos aprender de ella para ser mejores programadores?

  • 780