Javascript es un lenguaje de programación multiparadigma pero, ¿qué significa eso y lo que implica? En este post explicaré los diferentes enfoques que existen en Javascript y cómo la programación funcional ayuda a resolver tareas de manera más simple.

Un poco de historia

Javascript se remonta al año 1995, cuando Brendan Eich tuvo la tarea de crear un lenguaje de programación para el navegador web Netscape. Este navegador actualmente se encuentra en desuso, pero era muy popular en la época:

Cuando se ideó el lenguaje, se tomaron influencias de otros lenguajes como C, Java, Scheme y Perl, entre otros. Esto hizo que se adoptaran características de cada lenguaje y así combinarlas en un único lenguaje de programación.

Las dos caras de la misma moneda

El hecho de que Javascript sea multiparadigma significa que diferentes problemas se pueden resolver de varias maneras distintas, es decir, no estamos sujetos a una solución establecida previamente. Para entender mejor este concepto, pondré como ejemplo una función que, dado un conjunto de elementos, devuelve la suma total de dichos elementos.

En programación imperativa (o convencional, la que usaríamos en C por ejemplo) la función podría definirse de la siguiente manera:

function suma(conjunto) {
  var resultado = 0;                             // declaramos la variable que almacenará el resultado de la suma
  for (var i=0; i < conjunto.length; i++) {      // recorremos el conjunto de valores
    resultado += conjunto[i];                    // vamos añadiendo el valor de cada elemento al resultado
  }
  return resultado;                              // devolvemos el resultado de la suma
}

Luego podríamos usarla con un array y obtendríamos la suma de los elementos:

suma([1,-2,3]); // => 2

Hasta aquí todo bien, ahora viene cuando la cosa se complica un poco. Si quisiéramos cambiar el enfoque que le hemos dado a la función, tendríamos que descomponerla en otras funciones más pequeñas…

Espera un momento… ¿qué?

Tienes razón, antes de ponerme a hacer la otra versión, debería explicar antes qué es la programación funcional:

La programación funcional es un paradigma estilo de programación en el que aparecen, entre otras, las siguientes características:

  • Las funciones son de orden superior. Este concepto matemático especifica que este tipo de funciones son aquellas que cumplen al menos una de estas condiciones:
    • Pueden tomar una o más funciones como argumento.
    • Pueden devolver una función como resultado.
  • Funciones puras: son aquellas funciones que no tienen efectos secundarios:
    • No alteran la memoria.
    • No alteran sus parámetros de entrada.
    • Para la misma entrada se produce siempre la misma salida.
  • Transparencia referencial: este término se refiere a la posibilidad de sustituir una expresión cualquiera en el código del programa por otra equivalente sin que afecte a la funcionalidad del propio programa. Por ejemplo, se puede observar en la siguiente función pura:
    function sumar1(numero) {
    return numero + 1;
    }
    

    que sumar1(x) === sumar1(y) siempre y cuando x === y, e incluso sustituir la propia función por la expresión x + 1 o y + 1 respectivamente.

El hecho de que existan estas propiedades dotan a los lenguajes que las implementan de una simplicidad que no suele darse en los lenguajes de programación imperativa, entendiéndose simple como algo ‘conciso y concreto’.

Me ha quedado claro, volvamos al ejemplo anterior

De acuerdo. En el ejemplo anterior, estábamos sumando todos los elementos de un conjunto, algo que en matemáticas quedaría denotado de la siguiente manera:

En programción funcional podríamos representar esta expresión mediante dos funciones más sencillas. La primera sería la operación de suma, la cual acepta dos parámetros y devuelve la suma entre los dos:

function sumar(a, b) {
  return a + b;
}

Y la otra función sería la de acumulación (también conocida como fold o reduce). Esta función toma como parámetros una función que acepta dos argumentos, un valor inicial (conocido como acumulador) y una lista sobre la que aplicar las transformaciones. La función pasada como argumento es llamada con el acumulador y el primer (o último) elemento de la lista, produciendo un nuevo acumulador. Luego, la función se vuelve a llamar junto al nuevo acumulador y al nuevo primer (o último) elemento de la lista, y así sucesivamente. Cuando se ha recorrido la lista completa queda un único acumulador, el cual es el resultado final.

Como ejemplo, tenemos la siguiente lista: [1, 2, 3], la función sumar definida anteriormente y como acumulador el elemento neutro de la suma, 0 1.

La función de acumulación empezaría tomando el valor 0 del acumulador y el primer elemento de la lista (1) y le aplicaría la función sumar devolviendo 1, nuestro nuevo acumulador. En la siguiente iteración se aplicaría la función sumar al nuevo acumulador 1 con el siguiente elemento de la lista (2), resultando en el valor 3. Y para acabar, la función sumar se aplica al último elemento de la lista (3) con el anterior acumulador, obteniendo como resultado el valor 6.

En Javascript podríamos implementarla con el siguiente código:

function fold(lista, funcion, acumulador) {
  if (lista.length === 0) return acumulador;
  // devolvemos el acumulador si la lista está vacía
  else if (lista.length === 1) return funcion(lista[0], acumulador);
  // si hay un único elemento en la lista, devolvemos la aplicación de la función pasada como argumento al acumulador y al elemento de la lista
  else return funcion(acumulador, fold(lista.slice(1), funcion, lista[0]));
  // para más de un elemento, llamamos a la función fold dentro de la función pasada como argumento
}

Para no tener que usar esta implementación, Javascript posee un método dentro del tipo Array llamado reduce:

[/* elementos */].reduce(function(a, b) {
  // código de la función
}, /* acumulador (opcional) */);

Esta facilidad nos permite solucionar nuestro problema de la siguiente manera funcional:

function suma(lista) {
  return lista.reduce(function(a, b) {
    return a + b;
  }, 0);
}

Y al aplicarla sobre el ejemplo inicial:

suma([1,-2,3]); // => 2

¿Tanto lío para eso?

Si bien es cierto que la resolución de problemas de manera funcional puede parecer al principio más difícil que la programación imperativa, obtenemos los beneficios de los que hemos hablado antes (transparencia referencial, funciones de orden superior, estado hermético), por ejemplo en caso de necesitar mantener un entorno constante, o en situaciones de concurrencia, no producir errores de integridad. En cualquier caso, el estilo de programción siempre queda a criterio del desarrollador o en función del desempeño final del problema.

  1. El elemento neutro de la suma es aquel valor que, sumado a otro, devuelve el mismo valor: