Higher-Order Functions

Estoy seguro de que en más de una ocasión han escuchado hablar de las Higher-Order Functions, pero ¿qué características tienen estas funciones? Y más importante aún ¿para qué nos sirven en nuestro día a día?

Las Higher-Order Functions son cualquier función que cumpla con cualquiera de las siguientes dos características:

  • Reciben una función como parámetro
  • Retornan una función

Estas dos características le permiten a las Higher-Order Functions generalizar un patrón de código y especializarlo por medio de las funciones que le pasamos como parámetros o de las funciones que retornamos, veamos un ejemplo con la función map de los arrays, nosotros sabemos que esta función nos permite aplicar una transformación sobre cada uno de los elementos de un array, pero empecemos por un caso en específico, el de multiplicar por dos cada uno de los elementos de un array

function multiplyBy2(array) {

const newArray = [] // Crear nuevo array

for(const number of array) { // Iteramos sobre el array original

newArray.push(number*2) // Importante: aqui aplicamos la transformaciòn

}

return newArray

}

Esta función trabaja bien, pero es solo un caso específico de un sin fin de transformaciones que podemos aplicar sobre un array, así que usemos las Higher-Order Functions para generalizar el algoritmo y luego especializarlo

function map(mapper, array) {

const newArray = [] // Crear nuevo array

for(const number of array) { // Iteramos sobre el array original
newArray.push(mapper(number)) // usamos la funciòn mapper aplicar la transformaciòn
}
return newArray
}
// Uso
const numbers = [1, 2, 3]
map(number => number*2, numbers) // [2, 4, 6]


Esta función map es una Higher-Order Functions porque recibe una función como parámetro, esta función map generaliza un patrón de código que es el que nos permite aplicar una transformación sobre un array y lo especializamos a nuestro caso específico de multiplicar por 2 con la función pasada por parámetro, tanto el array sobre el cual iteramos como la transformación que aplicamos puede ser cualquiera.

Ahora veamos otro ejemplo, imaginemos que tenemos en nuestras aplicaciones muchas funciones que hacen peticiones http y a su vez queremos que si estas fallan registrar ese error en un sistema de registro de errores, nosotros podríamos escribir un código como el siguiente:

async function getUsers() {

try {

const users = await fetchingLibrary.get(‘/users’)

return users

} catch (error) {

logError(error)

throw error

}

}

En este código hacemos la petición, si hay algún error hacemos log del error y hacemos throw del error para que el consumidor de la función getUsers() maneje el error como le sea conveniente, ahora imaginen hacer esto por cada una de sus funciones que manejan errores, es repetitivo y es un detalle poco relevante a la hora de hacer la petición así que usemos nuestra nueva herramienta para mejorar esto

function withErrorLogging(fn) {

return async function executeOperation(…args) {

try {

const response = await fn(…args)

return response

} catch (error) {

logError(error)

throw error

}

}

}

// Uso

// Esta funciòn getUsers es equivalente a la funciòn que vimos al inicio const getUsers = withErrorLogging(() => {

return fetchingLibrary.get(‘/users’)

})

// Y podemos usar getUsers para muchas otras funciones

const getPostByID = withErrorLogging((id) => {

return fetchingLibrary.get(`/product/${id}`)

})

// Uso de getUsers

const users = await getUsers()

La función withErrorLogging recibe una función como parámetro que esperamos que retorne una promesa y retorna otra función que llamamos executeOperation que se encargara de recolectar todos los argumentos que se le pasen usando el rest operator de Javascript, ejecutara la función que pasamos como parámetro y retornara el resultado o hará el registro del error en caso de error, la ventaja de esta higher order function es que esta función withErrorLogging puede ser usada para agregarle a cualquier otra función la capacidad de registro de errores.


Espero que les haya gustado y por ultimo les comparto una serie de links que les pueden interesar

promises-fn-utils Esta librería la hice yo y su idea es tener una serie de Higher-Order Functions para agregar capacidades a funciones que retornan promesas, como politicas de reintento, cache, batching y manejo de concurrencia.

Post de Kent C Dodds que habla un poco sobre este tema explicando inversiòn de control

Patròn de diseño template Este patrón de diseño es la contraparte de las Higher-Order Functions en el mundo de la programación orientada a objetos

Alejandro Garcia Serna
Alejandro Garcia Serna
Software Engineer

¡Comparte este artículo!