Qué significa programación funcional
Entendemos por programación funcional un lenguaje de programación declarativo donde
elprogramador especifica lo que quiere hacer, en lugar de lidiar con el estado de los
objetos. Es decir, las funciones estarían en un primer lugar y nos centraremos en
expresiones que pueden ser asignadas a cualquier variable.
Al escribir el código de forma declarativa, se busca que no juegue con los objetos y sea
máslegible. Lo normal sería que un desarrollador tuviera que hacer un bucle, iterando y
crear una lógica pero, con el lenguaje funcional, este te da funciones que hace que se
parezca más a leer y escribir que a programar.
El origen del modelo de programación funcional, pese a ser algo de relativa reciente
aceptación, tiene su origen en en el cálculo lambda. El cálculo lambda es un sistema
desarrollado en la década de los 30 del siglo XX, donde buscaban investigar la naturaleza
de las funciones y la computabilidad.
Entonces, aprendiendo a escribir con lambdas podremos hacer uso de la programación
funcional. Antes de continuar, debemos dejar claro que las lambdas no es la única forma
delenguaje funcional pero sí la más extendida y que, por ejemplo, Javascript es la que
acepta.
Puedes pensar en una expresión lambda como un método anónimo. Tiene parámetros y
uncuerpo como los métodos completos, pero no tiene un nombre como un método real.
Las expresiones lambda se conocen como lambdas a secas.
Veamos un ejemplo más claro de cómo funcionan las lambdas. Tenemos un listado de
animales, con unas características. Hemos especificado la clase y sus propiedades aquí:
[Link] = species;
[Link] =
canSwim;[Link] =
canHop;
}
public String getSpecies() { return species; }
public boolean canSwim() { return
canSwim; }public boolean canHop() {return
canHop; }
}
Creamos una lista donde añadimos algunos objetos de ejemplo que nos ayudarán a dar
forma al código que queremos mostrar.
Si sobre esta lista de animales, quisiéramos obtener aquellos cuyo nombre comienza por
laletra D, podríamos resolver este problema escribiendo un bucle, o de forma funcional en
una sola línea. La primera parte del siguiente código, hace todo por nosotros. En ella se
hace uso de la función removeIf() que toma un String y devuelve un booleano.
De esta manera, en un solo paso quitamos todos los elementos que no nos interesan, y
enla siguiente línea recorremos todos los elementos restantes para pintar su nombre.
Otra forma de trabajar con programación funcional son los streams. Los streams son,
básicamente, una forma por la cual, conseguimos una fuente de datos, realizamos cero o
más operaciones intermedias y obtenemos un resultado.
Las partes de un stream pueden separarse en tres grupos:
Obtención del stream (la fuente)
Hacer el trabajo (las operaciones
intermedias)Obtener un resultado
(operación finalizada)
Con los streams, podemos obtener de una manera más simple y optimizada cosas que de
otra manera nos llevarían más código e iteraciones sobre los datos. Por ejemplo, de
nuestralista de animales anterior ¿cómo obtendrías los 2 primeros elementos que pueden
nadar pero no saltar ordenados por nombre?.
Tenemos menos código, operaciones más claras, no se apilan bucles y sin variables
temporales. De este modo nuestro código refleja lo que queremos hacer con los datos y
yano necesitamos preocuparnos de iteraciones.
Características de la programación funcional
En el lenguaje de programación funcional, contamos con algunas características clave
quelo diferencian de otros paradigmas de programación. Algunas de sus características
ya lashemos visto pero, en resumen son las siguientes:
No existen efectos colaterales: Una función, si tiene todos los parámetros definidos
porvalor y no se hacen asignaciones a variables globales, no tendrá efectos colaterales.
El valor de una lambda no depende de nada más que de los valores de
sussubexpresiones, si las tuviera.
Tiene una semántica limpia:
Únicamente significa lo que dice, es decir, estamos ante un lenguaje declarativo y siempre
devolverá el mismo resultado, mientras que el lenguaje imperativo no es así.
El almacenamiento de datos es implícito, por lo que las operaciones asignan
almacenamiento solo cuando es necesario y luego se libera automáticamente si se
vuelveinaccesible.
Como señalamos al comienzo, las funciones se vuelven valores de primera clase y
estaránal mismo nivel que cualquier otro valor. Una función podrá ser el valor de una
expresión, pasarse como argumento y colocarse en una estructura de datos.
Qué lenguajes de programación funcional existen
En la actualidad contamos con un buen puñado de lenguajes funcionales y paradigmas
deprogramación que los aceptan. Aquí te dejamos un listado con los principales lenguajes
deprogramación funcional:
LIS
ML
Hask
ell
OCa
mlF#
Erlan
Cloju
re
Scala
Además, existen muchos lenguajes de programación conocidos con los que podremos
aplicar modelos de programación funcional entre sus paradigmas:
Per
Ru
by
Visual Basic .NET
Dylan
Javascri
pt.
Python.
Ventajas y desventajas de la programación funcional
Como casi todos los lenguajes, este tiene sus ventajas y desventajas, por ahora ya hemos
mencionado alguna de ellas, pero veamos más en detalle:
Los programas no cuentan con estados.
Es una forma de programar muy adecuada para la paralelización.
Es un código fácilmente testable y verificable, incluso en las funciones que no cuentan con
estado.
Se trata de un código más corto, sencillo, legible y preciso.
Fácilmente combinable con la programación orientada a objetos e imperativa.
Ahora veamos los inconvenientes, que también existen algunos importante a tener en
cuenta a la hora de adoptar el modelo de programación funcional.
Los datos no pueden modificarse, es decir, las variables.
No es recomendable usarlo para conexiones de bases de datos y/o servidores. Además,
nocuenta con un acceso eficiente a grandes cantidades de datos.
No es la mejor opción para recursiones de la misma pila.
Podemos tener errores graves en la programación recurrente.
Lambdas
En 2014 Java lanza una versión el cual añade numerosas novedades importantes allenguaje
y que divide el antes y después de esta versión.
Las novedades más importantes que podemos destacar son el agregado del paquete
stream y la implementación del paradigma de programación funcional. Recordemos que
un paradigma no es más que una forma de resolver un problemaplanteado desde un
enfoque diferente.
Este paradigma es declarativo, por lo que nos enfocaremos en el qué y no en cómose esta
resolviendo el problema, es decir, nosotros expresaremos nuestra lógica sin describir
controles de flujo; ciclos y/o condicionales.
La programación orientada objetos es imperativa que no es mas que decir el controlde flujo
(Ciclos y Condicionales) a nuestro software.
Las interfaces funcionales son aquellas interfaces que poseen si y sólo si un solométodo
abstracto (sin cuerpo).
Puede poseer o no la notación @FunctionalInterface que previene que otro desarrollador
agregue mas métodos abstractos generando errores en tiempo decompilación.
Adicionalmente puede tener métodos por defecto y estáticos sin restricciones.
Para poder implementar el paradigma funcional se agrega el concepto de lambdasque no es
mas que un método anónimo, básicamente es un método abstracto quesólo está definido
en una interfaz (No necesitan una clase) y está debe ser una interfaz funcional.
Estructura
Parámetros:
1. Cuando se tiene un solo parámetro no es necesario utilizar los paréntesis.
2. Cuando no se tienen parámetros, o cuando se tienen dos o más, es necesarioutilizar
paréntesis.
3. Esta implícito el tipo de objeto que va a utilizar por lo que no es necesario
declararlo en el parámetro.
El operador lambda:
Operador flecha (->) que separa la declaración de parámetros de la declaración delcuerpo
de la función.
Cuerpo:
1. Cuando el cuerpo de la expresión lambda tiene una única línea no es necesarioutilizar
las llaves y no
necesitan especificar la sentencia return en el caso de que deban devolver valores.
2. Cuando el cuerpo de la expresión lambda tiene más de una línea se hace
necesario utilizar las llaves
Entidades de [Link]
Antes de crear una función de tipo lambda, conviene conocer las entidades básicas que
componen esta manera de programar. Las principales entidades son interfacescon un
único método que debe implementar el programador y que estas implementaciones
pueden hacerse llegar como argumentos de métodos de otras muchas clases del API de
Java. Hubo una gran modificación de las clases existentes para aceptar este tipo de
implementaciones allí donde tuviera sentido, como ocurre en las colecciones.
Las implementaciones de estas interfaces son del tipo, consume un valor y retorna otro
tipo de valor, o produce un valor sin argumentos o produce un valor dados dos
argumentos. A éstas se les llama unidades funcionales porque componen una lógicainterna
que a priori el consumidor de esta lógica no conoce, pero de la que sí se conoce su interfaz
y por tanto la manera de relacionarse con el resto de los objetos, o lo que es lo mismo la
manera de ser invocada. Aparece de nuevo el concepto de cajas negras en donde entran
parámetros y salen resultados.
Las interfaces funcionales más importantes contenidas en [Link] son:
Supplier: esta función se debe utilizar cuando se necesiten generar objetos sinrequerir
argumentos. Por ejemplo para realizar una inicialización perezosa.
Consumer esta en cambio es el opuesto de Supplier ya que consume, acepta como
argumento el tipo T sin devolver ningún valor de retorno.
Function<T,R> esta interfaz permite definir una función que acepta un parámetro detipo T
y devuelve un resultado del tipo R pudiendo aplicarle alguna transformación uoperación.
BiFunction<T,R,S> esta interfaz permite definir una función que acepta dos parámetros de
tipo T y R, devolviendo un resultado del tipo S. Normalmente seránoperaciones de
agregación u operadores binarios como la suma, resta, etc..
Predicate la interfaz predicado debe devolver forzosamente un boolean dado unobjeto de
tipo T, normalmente utilizado para filtrar elementos de una colección.
El paquete incluye más interfaces que el programador puede usar, pero estas sonlas
más básicas con las que ya es posible empezar a realizar algunos ejemplos útiles y
frecuentes.
Cómo se crea una función lambda
La sintaxis cambia un poco respecto a Java tradicional, ya que se intenta no escribirlos tipos
de las variables siempre y cuando no se cree alguna ambigüedad. Veamos el primer
ejemplo:
Function<String,Integer> sizeOf = (String s) -> {
return [Link]();
};
O su equivalente y más compacta: Function<String,Integer>
sizeOf = s -> [Link]();
En ambos casos se está definiendo una función que dado un String devolverá la longitud
de la cadena de caracteres que almacene. Fíjese que el tipo de la variablede s se infiere
automáticamente de los tipos utilizados en sizeOf y que la palabra reservada return no es
necesaria, siempre y cuando no haya más de una sentenciaen la función.
Por rara y compacta que pueda parecer la sintaxis, no es más que otra forma de escribir la
siguiente clase, de hecho esto es lo que realmente genera el compilador:
public class SizeOf implements Function<String,Integer>{public
Integer apply(String s){
return [Link]();
}
}
Así que para poder usar tanto la función sizeOf como la clase SizeOf en un bloquede
código cualquiera, se realizaría de la siguiente forma:
Integer r1 = [Link]("hola java 8");
// o
Integer r2 = new SizeOf().apply("hola java 8");
La ventaja de hacerlo como función, en lugar de como clase, a parte de la reducciónde los
literales que acompañan a cada opción, es que el API del JDK de Java versión 8 y adelante
tiene métodos que aceptan estas funciones, reduciendo aun más la cantidad de código
que se debe escribir.
Por ejemplo veamos como podemos aplicar una ordenación usando un comparadorescrito
en Java 8:
class Persona{
String nombre;
Persona (String nombre){
[Link] = nombre;
}
}
List<Persona> personas = new ArrayList<>();
[Link](new Persona("Pepe"));
[Link](new Persona("Andrés"));
[Link]( (l, r) -> [Link]([Link]));
Esta última función lambda es una del tipo BiFunction que acepta dos objetos de tipo
Persona y devuelve un int típico de cualquier comparador de java
[Link]<T>. Tanto la función como el comparador son compatibles, asíque la
función lambda anónima, se podría haber guardado en una variable de tipo comparador
para ser usado otra vez más adelante:
Comparator<Persona> comp = (l, r) -> [Link]([Link]));
[Link](comp);
El uso de lambda en colecciones
El uso más habitual de lambda es en las colecciones y se utiliza para definir una operación
que será aplicada a los elementos contenidos en ésta, dejando que el Apide Java realice la
iteración sobre los elementos por nosotros, sin que tengamos queescribir ninguna
sentencia de control de iteración como for o while.
Para poder aplicar funciones lambda en colecciones, en Java 8, se introduce una nueva
entidad denominada Stream. Los Stream representan flujos de datos que pueden ser
infinitos, pero en general el flujo estará asociado a una colección finita como un ArrayList.
Así, en un Stream el programador puede «registrar» operacionesque se harán sobre una
lista, por ejemplo. Para ello las colecciones incorporan un método .stream() con el cual
acceder al correspondiente flujo.
Stream<Persona> stream = [Link]();
List<String> nombres = stream
// filtrado de los elementos que tienen nombre nullo
.filter(p -> [Link]!=null)
// aplicar una conversión, de Persona a String
// quedándonos con el nombre
map(p -> [Link])
// a partir de aquí se tiene un Stream<String>
// recolectar los elementos en una lista
.collect([Link]());