Una de las cosas más difíciles que tienes que hacer en programación es controlar la complejidad. Sin una consideración cuidadosa, el tamaño y la complejidad de un programa pueden crecer hasta el punto de confundir incluso al creador del programa.

De hecho, como dijo un autor:

"El arte de programar es la habilidad de controlar la complejidad" - Marijn Haverbeke

En este artículo desglosaremos un concepto importante de programación. Este concepto de programación puede ayudarlo a mantener la complejidad bajo control y a escribir mejores programas.

Al final de este artículo, sabrá qué es la programación funcional, los tipos de funciones que existen, los principios de la programación funcional y tendrá una comprensión más profunda de las funciones de orden superior (Higher Order functions).

Supongo que ya tiene conocimientos preexistentes de los conceptos básicos de las funciones. Los conceptos fundamentales de funciones no se tratarán en este artículo.

Si desea una revisión rápida de las funciones en JavaScript, he escrito un artículo detallado aquí.

¿Qué es la programación funcional?

La programación funcional es un paradigma de programación o un estilo de programación que se basa en gran medida en el uso de funciones puras y aisladas.

Tal como puede haber adivinado por el nombre, el uso de funciones es el componente principal de la programación funcional. Pero, el mero uso de funciones no se traduce en programación funcional.

En la programación funcional, utilizamos funciones puras, que son funciones que no tienen efectos secundarios. Explicaré qué significa todo esto.

Antes de profundizar en el artículo, comprendamos parte de la terminología y los tipos de funciones que existen.

Tipos de funciones

Hay cuatro tipos principales de funciones.

Funciones de primera clase

En JavaScript, todas las funciones son funciones de primera clase. Eso significa que pueden tratarse como cualquier otra variable.

Las funciones de primera clase son funciones que pueden asignarse como valores a variables, devolverse desde otras funciones y pasarse como argumentos a otras funciones.

Considera este ejemplo de una función pasada a una variable:

const holaMundo = () => {
	console.log("Hola, Mundo"); // Hola, Mundo
};
holaMundo();

Funciones Callback

Las funciones callback son funciones que se pasan a otras funciones como argumentos y son llamadas por la función en la que se pasan.

Simplemente, las funciones callback son funciones que escribimos como argumentos en otras funciones. No podemos invocar funciones callback. Se invocan cuando se llama a la función principal en la que se pasaron como argumentos.

Veamos un ejemplo:

valorDePrueba es una función que acepta un valor y una función callback prueba que devuelve "valor pasó la prueba" si el valor devuelve verdadero cuando se pasa a la función callback.

En este caso, la función callback es el segundo argumento que pasamos a la función valorDePrueba. Se invoca cuando se llama a la función valorDePrueba.

Funciones de orden superior (High Order Functions)

Las funciones de orden superior son funciones que reciben otras funciones como argumentos o devuelven una función.

En este artículo, voy a profundizar en las funciones de orden superior y por qué son una provisión tan poderosa. Por ahora, todo lo que necesita saber es que este tipo de funciones reciben otras funciones como argumentos o funciones de retorno.

Funciones asíncronas

Las funciones asíncronas son funciones que no tienen nombre y no se pueden reutilizar. Estas funciones se escriben normalmente cuando necesitamos realizar algo una vez y en un solo lugar.

Un ejemplo perfecto de una función asíncrona es lo que escribimos anteriormente en este artículo.

const chequearString = ValorDePrueba('Twitter',  valor  =>  typeof  valor === 'string');
chequeoString;

// Consulte el fragmento de código anterior. 

chequearString es una variable cuyo valor es una función. Pasamos dos argumentos a esta función.

'Twitter' es el primer argumento y el segundo es una función asíncrona. Esta función no tiene un nombre y solo tiene una tarea: verificar si el valor dado es una string.

meme-fcc

Principios de la programación funcional

Anteriormente en el artículo aludí al hecho de que el mero uso de funciones no se traduce en programación funcional.

Hay algunos principios que debemos comprender si nuestros programas deben calificar para el estándar de programación funcional. Echemos un vistazo a esos.

Evite mutaciones y efectos secundarios.

El primer principio de la programación funcional es evitar cambiar las cosas. Una función no debería cambiar nada, como una variable global.

Esto es muy importante porque los cambios a menudo provocan errores. Si una función cambia una variable global, por ejemplo, podría dar lugar a un comportamiento inesperado en todos los lugares donde se utiliza esa variable.

El segundo principio es que una función debe ser pura, lo que significa que no tiene efectos secundarios. En la programación funcional, los cambios que se realizan se denominan mutaciones y los resultados se denominan efectos secundarios.

Una función pura no hace ninguna de las dos. Una función pura siempre tendrá la misma salida para la misma entrada.

Si una función depende de una variable global, esa variable debe pasarse a la función como argumento. Esto nos permite obtener la misma salida para la misma entrada.

Aquí hay un ejemplo:

const edadLegalEnEEUU = 21;
const chequearEstadoLegal = (edad, edadLegal) => {
    return edad >= edadLegal ? 'Mayor de edad.' : 'Menor de edad.';
};
const johnEstado = chequearEstadoLegal(18, edadLegalEnEEUU);
johnEstado; // Menor de edad.
edadLegalEnEEUU; // 21

Abstracción

Las abstracciones ocultan detalles y nos permiten hablar sobre problemas en un nivel superior sin describir todos los detalles de implementación del problema.

Usamos abstracciones en casi todos los aspectos de nuestra vida, especialmente en el habla.

Por ejemplo, en lugar de decir "Voy a cambiar dinero por una máquina que una vez conectada muestra imágenes en movimiento acompañadas de sonido", lo más probable es que diga "Voy a comprar un televisor".

En este caso, comprar y televisor son abstracciones. Estas formas de abstracción facilitan mucho el habla y reducen las posibilidades de decir algo incorrecto.

Pero estará de acuerdo conmigo en que antes de usar términos abstractos como comprar, primero debe comprender el significado del término y el problema que abstrae.

Las funciones nos permiten lograr algo similar. Podemos crear funciones para tareas que es más probable que repitamos una y otra vez. Las funciones nos permite crear nuestras propias abstracciones.

Además de crear nuestras propias abstracciones, ya se han creado algunas funciones para que abstraigamos tareas que es más probable que hagamos una y otra vez.

Así que vamos a ver algunas de estas funciones de orden superior que ya existen para abstraer tareas repetitivas.

Filtrando Arreglos

Cuando trabajamos con estructuras de datos como arreglos, es más probable que nos encontremos en una situación en la que solo estemos interesados en ciertos elementos del arreglo.

Para obtener estos elementos podemos crear fácilmente una función para realizar la tarea:

function arregloFiltro(arreglo, prueba) {
    const arregloFiltrado = [];
    for (let elemento of arreglo) {
        if (prueba(elemento)) {
            arregloFiltrado.push(elemento);
        }
    }
    return arregloFiltrado;
};
const arregloMezclado = [1, true, null, "Hola", undefined, "Mundo", false];
const soloCadenas = arregloFiltro(arregloMezclado, elemento => typeof elemento === 'cadena');
onlyCadenas; // ['Hola', 'Mundo']

arregloFlitro es una función que acepta un arreglo y una función callback. Recorre el arreglo y agrega los elementos que pasan la prueba en la función callback en un arreglo llamada arregloFiltrado.

Con esta función podemos filtrar un arreglo y devolver los elementos que nos interesan, como en el caso de arregloMezclado.

Imagínese si tuviéramos 10 programas diferentes y en cada programa necesitáramos filtrar un arreglo. Tarde o temprano resultaría extremadamente tedioso reescribir la misma función una y otra vez.

Por suerte, alguien ya pensó en esto. Los arreglos tienen un método de filtro (filter) estándar. Devuelve una nueva arreglo con los elementos del arreglo que recibe que pasan la prueba que proporcionamos.

const arregloMezclado = [1, true, null, "Hola", undefined, "Mundo", false];
const stringArreglo = arregloMezclado.filter(elemento => typeof elemento === 'cadena')
arregloDeCadenas; // ['Hola', 'Mundo']

Usando el método de filtro estándar (filter) pudimos lograr los mismos resultados que logramos cuando definimos nuestra propia función en el ejemplo anterior. Entonces, el método de filtro (filter) es una abstracción de la primera función que escribimos.

Transformar Elementos del arreglo con Map

Imagine otro escenario en el que tenemos una serie de elementos pero nos gustaría realizar una determinada operación en todos los elementos. Podemos escribir una función para hacer esto por nosotros:

function transformarArreglo(arreglo, prueba) {
    const arregloTransformado = [];
    for (let elemento of arreglo) {
        arregloTransformado.push(prueba(elemento));
    }
    return arregloTransformado;
};
const edades = [12, 15, 21, 19, 32];
const doblarEdades = transformarArreglo(edades, edad => edad * 2);
doblarEdades; // [24, 30, 42, 38, 64];

Así, hemos creado una función que recorre cualquier arreglo dada y transforma todos los elementos del arreglo en función de la función callback que proporcionamos.

Pero nuevamente, esto se volvería tedioso si tuviéramos que reescribir la función en 20 programas diferentes.

Una vez más, alguien pensó en esto por nosotros, y afortunadamente los arreglos tienen un método estándar llamado map que hace exactamente lo mismo. Aplica la función callback en todos los elementos del arreglo dada y luego devuelve una nueva arreglo.

const edades = [12, 15, 21, 19, 32];
const doblarEdades = edades.map(edad => edad * 2);
doblarEdades; // [24, 30, 42, 38, 64];

Reducir arreglos con Reduce

Aquí hay otro escenario: tiene un arreglo de números, pero le gustaría calcular la suma de todos estos números y devolverla. Por supuesto, puede escribir una función para hacer esto por usted.

function reducirArreglo(arreglo, prueba, start) {
    let suma = start;
    for (let elemento of arreglo) {
        suma = prueba(suma, elemento)
    }
    return suma;
}
let numeros = [5, 10, 20];
let doblarNumeros = reducirArreglo(numeros, (a, b) => a + b, 0);
doblarNumeros; // 35

Al igual que en los ejemplos anteriores que acabamos de ver, l0s arreglos tienen un método de reducción estándar (reduce) que tiene la misma lógica que la función que acabamos de escribir.

El método reduce se utiliza para reducir un arreglo a un solo valor en función de la función callback que proporcionamos. También toma un segundo argumento opcional que especifica desde dónde queremos que comience la operación callback.

La función callback que proporcionamos en la función de reducción (reduce) tiene dos parámetros. El primer parámetro es el primer elemento del arreglo de forma predeterminada. De lo contrario, es el segundo argumento que proporcionamos en el método de reducción (reduce). El segundo parámetro es el elemento actual del arreglo.

let numeros = [5, 10, 20];
let doblarNumeros = numeros.reduce((a, b) => a + b, 10);
doblarNumeros;  // 45

//El ejemplo anterior utiliza el método de reducción para agregar todos los elementos del arreglo a partir de 10. 

Otros métodos de Arreglo útiles

Array.some()

Todas las arreglos tienen el método some que acepta una función callback. Devuelve verdadero si cualquier elemento del arreglo pasa la prueba dada en la función callback. De lo contrario, devuelve falso: (false)

const numeros = [12, 34, 75, 23, 16, 63]
console.log(numeros.some(elemento => elemento < 100)) // true

Array.every()

El método every es lo opuesto al método some. También acepta una función callback y devuelve verdadero (true) si todos los elementos del arreglo pasan la prueba dada en la función callback. De lo contrario, devuelve falso: (false)

const numeros = [12, 34, 75, 23, 16, 63]
console.log(numeros.every(elemento => elemento < 100)) // true

Array.concat()

El método concat, abreviatura de concatenar, es un método de arreglo estándar que concatena o une dos arreglos y devuelve una arreglo:

const arreglo1 = ['uno', 'dos', 'tres'];
const arreglo2 = ['cuatro', 'cinco', 'seis'];
const arreglo3 = arreglo1.concat(arreglo2);
arreglo3; // [ 'uno', 'dos', 'tres', 'cuatro', 'cinco', 'seis' ]

Array.slice()

El método slice es un método de arreglo que copia los elementos de un arreglo de un índice dado y devuelve un nuevo arrelgo con los elementos copiados. El método slice acepta dos argumentos.

El primer argumento recibe el índice desde el que empezar a copiar. El segundo argumento recibe el índice desde el que dejar de copiar. Devuelve una nuevo arreglo con los elementos copiados desde el índice inicial (exclusivo) al índice final (inclusivo).

Sin embargo, tenga en cuenta que el método de slice no utiliza indexación cero. Entonces, el índice del primer elemento del arreglo es 1, no 0:

const numeros = [1,2,3,4,5,7,8];
console.log(numeros.slice(1, 4)); // [ 2, 3, 4 ]

Conclusión

Espero que hayas disfrutado leyendo este artículo y hayas aprendido algo nuevo al mismo tiempo.

Hay muchos métodos de arreglos y cadenas que no mencioné en el artículo. Si lo deseas, tómate un tiempo para investigar un poco sobre esos métodos.

Traducido del artículo de Joel P. Mugalu - Functional Programming in JavaScript Explained in Plain English