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
- FP vs OOP: Pick Two por Brian Goetz
https://www.youtube.com/watch?v=HSk5fdKbd3o
¡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?
- 846