Anteriormente en este blog ya hemos hablado de programación funcional, concretamente sobre currificación y pattern matching. En esta ocasión estudiaremos un par de conceptos adicionales de la programación funcional y veremos cómo se pueden aplicar a la programación cotidiana.
Inmutabilidad de objetos
La inmutabilidad es la propiedad que tiene un objeto que no puede cambiar su estado. Como consecuencia, la inmutabilidad aporta muchas facilidades a la hora de razonar sobre nuestro código, ya que nos libera de pensar en los cambios sufridos por objetos a lo largo del programa. Además, los objetos inmutables son automáticamente seguros en hilos (o thread-safe), ya que pueden ser accedidos de manera concurrente sin consecuencias, al no poder modificarse.
Foto: "Inmutable" de dominio público.
En programación orientada a objetos generalmente asumimos que el estado de un objeto puede cambiar según los métodos que se llamen sobre el mismo. Sin embargo, en ocasiones podemos construir clases que den lugar a objetos inmutables. Aunque a priori esto pueda parecer poco útil, veamos algunos ejemplos de lo que se puede hacer aprovechando la inmutabilidad. En Java, por ejemplo, la clase String
es inmutable:
String saludo = new String("¡Hola campusMVP!");
// no modifica la cadena:
saludo.replaceAll("Hola", "Buenas");
Aunque podríamos esperar que la cadena ahora contuviese "¡Buenas campusMVP!"
, no es así, sino que el método ha devuelto una nueva cadena:
saludo = saludo.replaceAll("Hola", "Buenas");
Ahora la cadena referenciada por la variable saludo
sí contiene el resultado esperado, pero en un objeto distinto al inicial.
Nota: dadas sus ventajas, la inmutabilidad de las cadenas de texto es algo que comparten muchos lenguajes de programación y que no es exclusivo de Java. También lo son en C#, C++, Perl, Python... ¡Incluso JavaScript!
En Java podemos crear nuestras propias clases inmutables con la palabra clave final
, que evita que haya clases descendientes, y usando métodos que no cambien el estado de los datos miembro. Por ejemplo:
public final class Person {
private final String name;
private final int age;
// ...
}
En otros lenguajes, la inmutabilidad es una característica clave diferenciadora. Por ejemplo, R, un popular lenguaje orientado a análisis estadístico, es conocido por su extenso uso de la inmutabilidad, sin ser puramente funcional. En Rust, todas las variables son por defecto inmutables. La mayoría de lenguajes funcionales incorporan inmutabilidad y ausencia de variables, de forma que un identificador sólo puede hacer referencia a un valor y ese valor no se puede modificar.
En cualquier caso, la idea de usar objetos inmutables puede ser útil en diversos contextos, para proporcionar mayor seguridad y eficiencia así como facilidades para concurrencia sin necesidad de sincronización.
Funciones puras
Los lenguajes funcionales generalmente tratan de asegurar la transparencia referencial, es decir, que cualquier expresión del lenguaje se pueda sustituir por su valor sin que eso altere el comportamiento del programa. Para ello son necesarios objetos inmutables, pero también funciones que siempre devuelvan el mismo valor ante las mismas entradas.
Por ejemplo, consideremos una función que cuenta los caracteres de una entrada por pantalla, que se correspondería con el siguiente código Python:
def input_length():
len(input())
¿Podemos asegurar que la llamada input_length()
devolverá siempre el mismo valor? No, puesto que el usuario podría introducir distintas cadenas. Esto se debe a que la función accede a información externa a los parámetros que recibe.
Adicionalmente, una propiedad deseable sería que una función no modificase elementos fuera de su ámbito, es decir, no tuviese efectos secundarios, como pueden ser escribir a un archivo o modificar variables globales. Una vez que una función siempre devuelve las mismas salidas para las mismas entradas y no tiene efectos secundarios, se la denomina función pura.
Las funciones puras son útiles porque se pueden ejecutar desordenadamente y en paralelo, cuando no hay dependencias de parámetros entre ellas. Ejemplos de estas son cualquier función matemática y algoritmos deterministas. Son funciones impuras, por otro lado, los generadores de números aleatorios.
Espero que este artículo te haya ayudado a entender las ventajas de usar ideas provenientes de la programación funcional, y que las puedas aplicar en tu día a día de la programación.
Fuentes y más información: