Skip to content

T5.11 Trabajo con Arrays con programación funcional

Otra forma diferente de trabajar con arrays es mediante la programación funcional, donde nosotros definiremos la función que queremos aplicarle a un array y será eésta la que le daremos al mismo, siendo éste (el array) el que se encargue de utilizarla según le indiquemos.

Para realizar ésto tenemos que comenzar por introducir el concepto de función callback.

Función Callback: es una función que se pasa a otra función como un argumento, que luego se invoca dentro de la función externa para completar algún tipo de rutina o acción.

Veámos algunas opciones donde utilizar estas funciones callback con arrays en Javascript.

En JS, los arrays son objetos con métodos que aceptan funciones callback. Estas funciones serán aplicadas a los datos disponibles en el array.

Algunos métodos disponibles en los arrays:

  • .forEach() consume un array: vector.forEach( callback );. No devuelve nada.
  • .map() modifica o transforma un array: vector.map( callback );. Devuelve el array transformado según la función callback facilitada.
  • .filter() filtramos los elementos deseados del array: vector.filter( callback );. Devuelve sólo los elementos que cumplian la función (valor true).
  • .reduce() pasa un array a un escalar siguiendo la función suministrada: vector.reduce( callback [, inicial] ); (el valor inicial es opcional). Devuelve un escalar (un número).
  • .reduceRight() igual, pero de derecha a izquierda.
  • .every() verifica si TODOS los elementos cumplen una condición (boolean)
  • .some() verifica si existe algún elemento que cumple la condición.

Donde las funciones callback podrían ser respectivamente:

const mostrar = e => console.log(e);
const doblar = e => 2*e;
const esPar = e => (e%2)==0;
const suma = (acumulado, e) => { return acumulado + e; };

Recuerda que siempre podemos recurrir a la expresión general de lambda:

const double = (e) => { return 2*e; }

forEach() - Consumo

Consumo de los elementos de un array con la función callback proporcionada:

let numeros = [1, 2, 3, 4, 5];

numeros.forEach( mostrar );

Podemos proporcionarla una función clásica, una función anónima o una lambda / arrow function.

map() - Transforma

Modifica cada elemento del array según la función proporcionada:

let arrayDeDobles = entrada.map( doblar );

arrayDeDobles.forEach( mostrar );

Y como el método map() devuelve un array modificado podemos concatenar operaciones, por ejemplo:

entrada.map( doblar ).forEach( mostrar )

Nota: el método forEach NO devuelve nada, por lo que no podremos concatenar con ningún método.

filter() - Filtrar

“Deja pasar” sólo aquellos valores que cumplen la función (resultado truthy):

let arrayDePares = entrada.filter( esPar );
console.log(`Original: [${entrada}] -> filtrado -> [${arrayDePares}]` );

reduce() - Reducción a escalar

Realizará una operación acumulativa sobre los elementos del array dando como resultado un único dato escalar:

let valorDeLaSuma  = entrada.reduce( suma, 10 );
console.log(`Suma de [${entrada}] = ${valorDeLaSuma}`);

// También podríamos reducir de una forma más imperativa y verbosa
let valorFinal = 0;
const acumular = e => valorFinal += e;
entrada.forEach( acumular );
console.log(`ValorFinal: ${valorFinal}`);


// Reduciendo arrays incompletos:
let arregloIncompleto = [1];
arregloIncompleto[4] = 5;
let concatenar = (acumulado, e) => acumulado + ", "+ e;
let concatenado  = arregloIncompleto.reduce( concatenar );
console.log(`Todos los elementos del [${arregloIncompleto}] = ${concatenado}`);

every() - ¿Todos?

Todos cumplen la condición de la función callback:

let sonTodosPares = entrada.every( esPar );
console.log(`¿Todos los elementos son pares? ${sonTodosPares}`);

let sonTodosMenoresDe5 = entrada.every( e => e < 6 );
console.log(`¿Todos los elementos son menores de 6? ${sonTodosMenoresDe5}`);

// Al vuelo:
// 1. Creamos el array
// 2. Iteramos sobre él...
// 3. ... con la lambda
console.log(`¿Todos los elementos son mayores que 4? ${ [5, 6, 7].every( e => e > 4 ) }`);

any() - ¿Alguno?

¿Alguno cumple el callback?

let hayAlgunoPar = entrada.some( esPar );
console.log(`¿Hay algún elemento par? ${hayAlgunoPar}`);

Buenas prácticas

ℹ️ Fuente original: repo de Brayan Diaz C

  • Utilizar métodos de array como forEach(), map(), filter(), reduce(), etc. en lugar de bucles for. Estos métodos son más legibles y menos propensos a errores.
  • Ser cuidadoso al utilizar la indexación de array (array[i]) dentro de un bucle. Asegurarse de que el índice sea válido y de que no se produzca un desbordamiento o subdesbordamiento.
  • Utilizar nombres descriptivos y legibles para las variables del bucle y para los elementos del array. Esto hace que el código sea más fácil de entender y depurar.
  • Utilizar el método Array.isArray() para verificar si un valor es un array antes de iterar sobre él. Esto es más seguro que utilizar la comprobación de tipo typeof.
  • Utilizar la sintaxis de desestructuración para acceder a los elementos de un array en lugar de utilizar la indexación de array. Esto hace que el código sea más legible y menos propenso a errores.
  • Evitar la mutación directa de los elementos del array dentro del bucle, especialmente cuando se trata de arrays anidados. En su lugar, utilizar métodos de array que no mutan el array original, como map(), filter(), reduce(), etc.
  • Utilizar la declaración de const en lugar de let para declarar la variable del bucle si no se va a modificar dentro del bucle.

Siguiendo estas buenas prácticas al iterar con arrays en JavaScript, podemos escribir código más legible, eficiente y menos propenso a errores sutiles.

Ventajas e inconvenientes de las lambdas

Ventajas de las Arrow Functions

ℹ️ Fuente original: repo de Brayan Diaz C

  • Son más concisas y fáciles de leer que las funciones tradicionales.
  • Tienen un contexto léxico this más claro y predecible que las funciones tradicionales.
  • Permiten crear funciones anónimas de manera más simple y elegante.

Desventajas de las Arrow Functions

ℹ️ Fuente original: repo de Brayan Diaz C

  • No pueden ser usadas como constructores (no tienen un objeto prototype).
  • No tienen un arguments propio, por lo que si se desea acceder a los argumentos de la función, se deben usar argumentos predeterminados o rest parameters.

Buenas Prácticas con Arrow Functions

ℹ️ Fuente original: repo de Brayan Diaz C

  • Utilizar arrow functions en lugar de funciones anónimas en callbacks. Las arrow functions son más cortas, legibles y evitan el uso de la palabra clave this.
  • Utilizar paréntesis alrededor de los parámetros de la función si hay más de uno. Esto hace que el código sea más legible y evita errores sutiles.
  • Utilizar la sintaxis de una sola línea (=>) para arrow functions que tienen una sola expresión en el cuerpo. En su lugar, utilizar la sintaxis de bloque ({}) para arrow functions que tienen varias líneas de código en el cuerpo.
  • Utilizar la declaración de const en lugar de let para asignar arrow functions a variables. Esto evita que la variable sea reasignada accidentalmente.
  • Utilizar nombres descriptivos y legibles para las variables que contienen arrow functions. Esto hace que el código sea más fácil de entender y depurar.
  • Evitar el uso excesivo de arrow functions. Las arrow functions son útiles para funciones simples y para callbacks, pero pueden hacer que el código sea más difícil de leer si se utilizan en exceso.
  • Asegurarse de que el contexto de this sea el deseado dentro de la arrow function. En algunos casos, puede ser necesario utilizar la función bind() para establecer explícitamente el contexto de this.