Original article: Higher Order Functions in JavaScript – Beginner's Guide

En JavaScript, las funciones son tratados como ciudadanos de primera clase. Podemos tratar a las funciones como valores y asignarlos a otra variable, pasarlos como argumentos a otra función, o inclusive regresarlos desde otra función.

Esta habilidad de las funciones de actuar como funciones de primera clase es lo que potencia a las funciones de orden superior en JavaScript.

Básicamente, una función que toma otra función como un argumento o regresa una función es conocido como una función de orden superior.

Group-35

Profundicemos un poco para ver ambos tipos de implementaciones, que son:

  • Pasar una función como argumento a otra función
  • Regresar una función desde otra función
63eec0636ec9b999bf8c5ee5340dd54a_w200

Cómo pasar una función como argumento a otra función

En esta sección, veremos como podemos enviar una función como argumento y al final cómo nos ayuda a escribir código más limpio.

Considera el siguiente código en el que queremos crear una función que acepta un arreglo como argumento. Filtra todos los números impares y regresa todos los números filtrados.

La función lucirá algo como esto:

const arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11];

function filterOdd(arr) {
  const filteredArr = [];
  for (let i = 0; i < arr.length; i++) {
    if (arr[i] % 2 !== 0) {
      filteredArr.push(arr[i]);
    }
  }
  return filteredArr;
}
console.log(filterOdd(arr));

// Salida:
// [ 1, 3, 5, 7, 9, 11 ]

La función de arriba regresa el arreglo filtrado [ 1, 3, 5, 7, 9, 11 ] donde tenemos todos los números impares, como se esperaba.

Ahora digamos que queremos también hacer una función que filtre y regrese todos los números pares. Podemos continuar tranquilamente y crear el siguiente código para lograr eso:

function filterEven(arr) {
  const filteredArr = [];
  for (let i = 0; i < arr.length; i++) {
    if (arr[i] % 2 == 0) {
      filteredArr.push(arr[i]);
    }
  }
  return filteredArr;
}
console.log(filterEven(arr));

// Salida:
// [ 2, 4, 6, 8, 10 ]

De nuevo, como se esperaba, obtendremos la salida deseada de un arreglo con todos los números pares dentro – [ 2, 4, 6, 8, 10 ].

Pero fíjate que estamos escribiendo un montón de código duplicado en este enfoque. Ambas funciones de arriba hacen un montón de cosas similares, como aceptar el arreglo original, crear un arreglo nuevo para almacenar el arreglo filtrado, recorrer todo el arreglo principal, y finalmente regresar el arreglo filtrado.

La única diferencia entre ambas funciones es la lógica que usan para filtrar el arreglo original.

Para la función filterOdd usamos la lógica de arr[i] % 2 !== 0 mientras que en la función filterEven usamos la lógica arr[i] % 2 === 0 para filtrar el arreglo original.

Aquí es donde nos podemos beneficiar de usar funciones de orden superior. La intención principal es crear una función para hacer todas las cosas similares que hicimos en las dos funciones de arriba y pasar la parte de la lógica aparte como argumento a esta función. Veamos cómo podemos implementar esto.

Hagamos la función que hace todas las cosas similares que hicimos en las funciones filterOddy filterEven. Esto iría algo así:

function filterFunction(arr, callback) {
  const filteredArr = [];
  for (let i = 0; i < arr.length; i++) {
    callback(arr[i]) ? filteredArr.push(arr[i]) : null;
  }
  return filteredArr;
}

Ignora el parámetro callback por ahora. Fíjate cómo en la nueva función filterFunction mantuvimos todos los pasos similares, que es aceptar el arreglo original, crear un nuevo arreglo para almacenar el arreglo filtrado, recorrer todo el arreglo principal, y finalmente regresar el arreglo filtrado que estuvimos ejecutando en las funciones filterOdd y filterEven.

Ahora el parámetro callback básicamente acepta la lógica, el cual no será más que otra función conteniendo la lógica de filtrado. Para filtrar los números pares e impares, respectivamente, aquí están las funciones de lógica que necesitamos escribir:

// Función que contiene la lógica para filtrar los números impares

function isOdd(x) {
  return x % 2 != 0;
}

// Función que contiene la lógica para filtrar los números pares

function isEven(x) {
  return x % 2 === 0;
}

¡Eso es todo! Ahora solo necesitamos pasar el arreglo principal, junto con la función de lógica, a nuestra función filterFunction así:

// Para filtar los números impares

filterFunction(arr, isOdd)
// Salida de console.log(filterFunction(arr, isOdd)):
// [ 1, 3, 5, 7, 9, 11 ]

// Para filtar los números pares

filterFunction(arr, isEven)
// Salida de console.log(filterFunction(arr, isEven)):
// [ 2, 4, 6, 8, 10 ]

De esta manera pasamos las funciones de lógica como son isOdd o isEven como argumentos a otra función filterFunction.

Básicamente, estamos abstrayendo la lógica de filtrado principal desde la función principal. Ahora podemos pasar cualquier otra lógica de filtrado, ya que no queremos cambiar la función filterFunction.

Por ejemplo, si queremos filtrar un número mayor a 5 entonces necesitamos escribir la siguiente lógica de filtrado:

function isGreaterThanFive(x) {
  return x > 5;
}

y pasarlo como argumento a filterFunction:

filterFunction(arr, isGreaterThanFive)

// Salida de console.log(filterFunction(arr, isGreaterThanFive)):
// [ 6, 7, 8, 9, 10, 11 ]

Podemos también pasar la función de lógica como una función de flecha y obtener el mismo resultado – es decir, pasar (x) => x > 5 en lugar de isGreaterThanFive nos dará el mismo resultado.

filterFunction(arr, (x) => x > 5)

// Salida de console.log(filterFunction(arr, (x) => x > 5)):
// [ 6, 7, 8, 9, 10, 11 ]

Cómo crear Polyfills

Sabemos que JavaScript nos provee con algunas funciones incorporadas de orden superior como map(), filter(), reduce(), y así sucesivamente. ¿Podemos recrear nuestra propia implementación de estas funciones? Indaguemos un poco más.

Ya creamos nuestra función de filtrado en la sección de arriba. Ahora creamos un prototipo de arreglo de nuestra función, filterFunction así podemos usarlo con cualquier arreglo. Eso lucirá algo así:

Array.prototype.filterFunction = function (callback) {
  const filteredArr = [];
  for (let i = 0; i < this.length; i++) {
    callback(this[i]) ? filteredArr.push(this[i]) : null;
  }
  return filteredArr;
};

En el código de arriba, this se refiere al arreglo en donde se llama el prototipo. Así que si escribimos algo como:

const arr = [1, 2, 3, 4, 5]
arr.filterFunction(callbackFn)

Entonces this se referiría al arreglo arr.

Ahora podemos usar la función filterFunction como usamos la función incorporada filter() en JS. Podemos escribir algo así:

arr.filterFunction(isEven)

que es similar a llamar la función incorporada filter():

arr.filter(isEven)

Ambas llamadas de las funciones de arriba (que serían arr.filterFunction(isEven) y arr.filter(isEven)) nos darán la misma salida,  [ 2, 4, 6, 8, 10 ].

Similarmente, podemos pasar una función flecha en nuestra implementación de prototipo como pasamos la función incorporada filter().

// I
arr.filterFunction((x) => x % 2 != 0)
arr.filter((x) => x % 2 != 0)
// ambos dan la misma salida en console.log: [ 1, 3, 5, 7, 9, 11 ]

// II
arr.filterFunction((x) => x > 5)
arr.filter((x) => x > 5)
// ambos dan la misma salida en console.log: [ 6, 7, 8, 9, 10, 11 ]

De una manera, hemos escrito un polyfill para la función incorporada filter().

Encadenamiento de función

También podemos implementar encadenamiento de funciones con nuestra implementación de prototipo, como podemos con la función incorporada filter(). Primero filtremos todos los números mayores a 5. Luego del resultado, filtraremos todos los números pares. Lucirá algo así:

// Usando nuestra propia implementación de prototipo filterFunction()
arr.filterFunction((x) => x > 5).filterFunction((x) => x % 2 === 0)

// Usando la implementación incorporada filter()
arr.filter((x) => x > 5).filter((x) => x % 2 === 0)

// ambos dan la misma salida en console.log: [ 6, 8, 10 ]

Así es como podemos usar funciones de orden superior en JS para escribir en modo modular, más limpio y, código más mantenible.

Ahora, miremos en cómo podemos regresar una función desde otra función.

lets-move-on-proceed

¿Cómo regresar una función desde otra función en JavaScript?

Podemos regresar una función desde otra función porque tratamos a las funciones en JavaScript como valores. Veámoslo con un ejemplo:

function calculate(operation) {
  switch (operation) {
    case "ADD":
      return function (a, b) {
        console.log(`${a} + ${b} = ${a + b}`);
      };
    case "SUBTRACT":
      return function (a, b) {
        console.log(`${a} - ${b} = ${a - b}`);
      };
  }
}

En el código de arriba, cuando invocamos la función calculate con un argumento, cambia según ese argumento y luego finalmente regresa una función anónima. Así que si llamamos a la función calculate() y almacenamos su resultado en una variable y lo ponemos en un console.log, tendremos la siguiente salida:

const calculateAdd = calculate("ADD");
console.log(calculateAdd);

// Salida: 
// [Function (anonymous)]

Podemos ver que calculateAdd contiene una función anónima que la función calculate() regresó.

Hay dos formas de llamar a esta función interna, el cual lo exploraremos ahora.

Llamar la función regresada usando una variable

En este método, almacenamos la función regresada en una variable como se muestra arriba y luego invocamos la variable en vez de llamar a la función interna.

Veámoslo en código:

const calculateAdd = calculate("ADD");
calculateAdd(2, 3);
// Salida: 2 + 3 = 5


const calculateSubtract = calculate("SUBTRACT");
calculateSubtract(2, 3);
// Salida: 2 - 3 = -1

Así que, ¿qué hemos hecho aquí?

  • Llamamos a la función calculate() y le pasamos ADD como el argumento
  • Almacenamos la función anónima regresada en la variable calculateAdd, e
  • Invocamos la función interna regresada llamando a calculateAdd() con los argumentos requeridos.

Llamar a la función regresada usando doble paréntesis

Este es una forma muy sofisticada de llamar a la función interna retornada. Usamos doble paréntesis ()() en este método.

Veámoslo en código:

calculate("ADD")(2, 3);
// Salida: 2 + 3 = 5

calculate("SUBTRACT")(2, 3);
// Salida: 2 - 3 = -1

Puedes verlo de la misma manera que nuestro ejemplo de arriba de encadenamiento. Sólo que en vez de encadenar las funciones, encadenamos los argumentos.

Los argumentos en el primer paréntesis pertenece a la función externa, mientras que los argumentos del segundo paréntesis pertenece a la función interna regresada.

El método calculate() regresa una función como se explicó anteriormente, y es esa función regresada la que es llamada inmediatamente usando el segundo paréntesis.

Como mencioné arriba, es una forma muy sofisticada de llamar a una función. Pero una vez que lo captes, se convierte en... bueno, bastante natural.

Un lugar donde podemos ver este tipo de notación de doble paréntesis es en el método connect en la librería de gestión de estado redux. Puedes leer más sobre connect aquí.

Resumen

En este artículo, aprendimos:

  • Por qué las funciones son llamadas ciudadanos de primera clase en JS
  • Qué son las funciones de orden superior
  • Cómo pasar una función como argumento a otra función
  • Cómo crear un prototipo de arreglo, encadenamiento de funciones, escribir nuestro propio polyfill para el método incorporado filter()
  • Cómo regresar una función desde otra función y diferentes formas de llamar a la función regresada

Concluyendo

¡Gracias por leer! Realmente espero que hayas encontrado útil a este artículo sobre funciones de orden superior. Mantente al tanto por más contenido maravilloso. Paz! 🖖

Redes Sociales

e2bd7ce3fc5f2783f1e210b015cc5fb1