Programación funcional en JavaScript explicada en inglés sencillo

Una de las cosas más difíciles que tiene 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 lo expresó 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 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.

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, escribí 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.

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

const helloWorld = () => { console.log("Hello, World"); // Hello, World }; helloWorld(); 

Funciones de devolución de llamada

Las funciones de devolución de llamada 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 de devolución de llamada son funciones que escribimos como argumentos en otras funciones. No podemos invocar funciones de devolución de llamada. Se invocan cuando se llama a la función principal en la que se pasaron como argumentos.

Veamos un ejemplo:

const testValue = (value, test) => { if (test(value)) { return `${value} passed the test`; } else return `${value} did not pass the test`; }; const checkString = testValue('Twitter', string => typeof string === 'string'); checkString; // Twitter passed the test 

testValuees una función que acepta un valor y una función de devolución de llamada test  que devuelve "el valor pasó la prueba" si el valor devuelve verdadero cuando se pasa a la función de devolución de llamada.

En este caso, la función de devolución de llamada es el segundo argumento que pasamos a la testValuefunción. Se invoca cuando testValuese llama a la función.

Funciones de orden superior

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

En este artículo, 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 asincrónicas

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

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

const checkString = testValue('Twitter', value => typeof value === 'string'); checkString; // Refer to previous code snippet

checkStringes 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 asincrónica. Esta función no tiene un nombre y solo tiene una tarea: verificar si el valor dado es una cadena.

Principios Meme

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 entender si nuestros programas deben calificar para el estándar de programación funcional. Veamos 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.

A pure function does neither of the two. A pure function will always have the same output for the same input.

If a function depends on a global variable, that variable should be passed to the function as an argument. This allows us to obtain the same output for the same input.

Here is an example:

const legalAgeInTheUS = 21; const checkLegalStatus = (age, legalAge) => { return age >= legalAge ? 'Of legal age.' : 'Not of legal age.'; }; const johnStatus = checkLegalStatus(18, legalAgeInTheUS); johnStatus; // Not of legal age legalAgeInTheUS; // 21 

Abstraction

Abstractions hide details and allow us to talk about problems at a higher level without describing all the implementation details of the problem.

We use abstractions in all almost all aspects of our lives, especially in speech.

For example, instead of saying "I'm going to exchange money for a machine that once plugged in displays moving images accompanied with sound", you are most likely to say "I'm going to buy a television".

In this case buy and television are abstractions. These forms of abstractions make speech a lot more easier and reduce the chances of saying the wrong thing.

But you'll agree with me that before using abstract terms like buy you need to first understand the meaning of the term and the problem it abstracts.

Functions allow us to achieve something similar. We can create functions for tasks that we are most likely to repeat again and again. Functions allows us to create our own abstractions.

On top of creating our own abstractions, some functions have already been created for us to abstract tasks that we are most likely to do time and again.

So we are going to look at some of these higher order functions that already exist to abstract repetitive tasks.

Filtering Arrays

When working with data structures like arrays, we are most likely to find ourselves in a situation where we are only interested in certain items in the array.

To obtain these items we can easily create a function to do the task:

function filterArray(array, test) { const filteredArray = []; for (let item of array) { if (test(item)) { filteredArray.push(item); } } return filteredArray; }; const mixedArray = [1, true, null, "Hello", undefined, "World", false]; const onlyStrings = filterArray(mixedArray, item => typeof item === 'string'); onlyStrings; // ['Hello', 'World'] 

filterArray is a function that accepts an array and a callback function. It loops through the array and adds the items that pass the test in the callback function into an array called filteredArray.

Using this function we are able to filter an array and return items that we're interested in, such as in the case of mixedArray.

Imagine if we had 10 different programs and in each program we needed to filter an array. Sooner or later it would become extremely tiresome to rewrite the same function over and over again.

Luckily someone already thought about this. Arrays have a standard filter method. It returns a new array with the items in the array it receives that pass the test that we provide.

const mixedArray = [1, true, null, "Hello", undefined, "World", false]; const stringArray = mixedArray.filter(item => typeof item === 'string') stringArray; // ['Hello', 'World'] 

Using the standard filter method we were able to achieve the same results we did when we defined our own function in the previous example. So, the filter method is an abstraction of the first function we wrote.

Transforming Array Items With Map

Imagine another scenario where we have an array of items but we would like to perform a certain operation on all the items. We can write a function to do this for us:

function transformArray(array, test) { const transformedArray = []; for (let item of array) { transformedArray.push(test(item)); } return transformedArray; }; const ages = [12, 15, 21, 19, 32]; const doubleAges = transformArray(ages, age => age * 2); doubleAges; // [24, 30, 42, 38, 64]; 

Just like that we  have created a function that loops through any given array and transforms all the items in the array based on the callback function the we provide.

But again this would grow tedious if we had to rewrite the function in 20 different programs.

Again, someone thought about this for us, and luckily arrays have a standard method called map which does the same exact thing. It applies the callback function on all the items in the given array and then it returns a new array.

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

Reducing Arrays with Reduce

Here's another scenario: You have an array of numbers, but you would like to compute the sum of all these numbers and return it. Of course you can write a function to do this for you.

function reduceArray(array, test, start) { let sum = start; for (let item of array) { sum = test(sum, item) } return sum; } let numbers = [5, 10, 20]; let doubleNumbers = reduceArray(numbers, (a, b) => a + b, 0); doubleNumbers; // 35 

Similar to the previous examples we just looked at, arrays have a standard reduce method that has the same logic as the function we just wrote above.

The reduce method is used to reduce an array to a single value based on the callback function that we provide. It also takes an optional second argument which specifies where we want the operation in the callback to start from.

The callback function we provide in the reduce function has two parameters. The first parameter is the first item in the array by default. Otherwise it is the second argument we provide into the reduce method. The second parameter is the current item in the array.

let numbers = [5, 10, 20]; let doubleNumbers = numbers.reduce((a, b) => a + b, 10); doubleNumbers; // 45 //The above example uses the reduce method to add all the items in the array starting from 10.

Other Useful Array Methods

Array.some()

All arrays have the some method which accepts a callback function. It returns true if any element in the array passes the test given in the callback  function. Otherwise it returns false:

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

Array.every()

The every method is the opposite of the some method. It also accepts a callback function and returns true if all the items in the array pass the test given in the callback  function. Otherwise it returns false:

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

Array.concat()

The concat method, short for concatenate, is a standard array method that concatenates or joins two arrays and returns a new array:

const array1 = ['one', 'two', 'three']; const array2 = ['four', 'five', 'six']; const array3 = array1.concat(array2); array3; // [ 'one', 'two', 'three', 'four', 'five', 'six' ]

Array.slice()

The slice method is an array method which copies the items of an array from a given index and returns a new array with the copied items. The slice method accepts two arguments.

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 nueva matriz con los elementos copiados desde el índice inicial (exclusivo) hasta el índice final (incluido).

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

const numbers = [1,2,3,4,5,7,8]; console.log(theArray.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 matriz y cadena que no mencioné en el artículo. Si lo desea, tómese un tiempo para investigar un poco sobre esos métodos.

¿Si quieres conectarte conmigo o simplemente saludar? no dude en hacerlo a través de Twitter. También comparto consejos y recursos interesantes para desarrolladores. ?