Introducción a los combinadores

Los combinadores son funciones de nivel superior que permiten combinar funciones, variables u otros combinadores. Normalmente, no contienen declaraciones de variables propias ni lógica de negocio. Son los únicos que permiten el control en un programa de funciones.

En este artículo intentaré tratar algunos de ellos:

Toque

Un combinador es muy útil para funciones que no devuelven nada. Toma la función a la que va el parámetro y luego lo devuelve.

Declaración

const tap = (fn) => (valor) => {
fn(valor);
devolver valor;
};

Ejemplo imperativo

const [items, setItems] = useState(() => [])

axios
.get('http://localhost')
.then({ datos } => {
setItems(datos)
console.log(datos)
onLoadData(datos)
}).then(...) // devuelve undefined - los datos de la promesa han sido modificados

Ejemplo declarativo

const [items, setItems] = useState(() => [])

axios
.get('http://localhost')
.then(({ datos }) => datos)
.then(tap(setItems)) // (datos) => { setItems(datos); return datos }
.then(tap(console.log)) // un then = una función = una responsabilidad
.then(tap(onLoadData))
.then(...) // todavía acceso a los datos originales
// fácil de mantener el principio de abrir/cerrar

Curry

Divide los argumentos de una función y permite llamarlos secuencialmente.

Declaración

const curry = (fn) => {
const curried = (...args) => {
if (fn.length !== args.length){
return curried.bind(null, ...args)
}
return fn(...args);
};

return curried

};


Ejemplo

const curry = (fn) => {
const curried = (...args) => {
if (fn.length !== args.length){
return curried.bind(null, ...args)
}
return fn(...args);
};

return curried

};
const suma = (a, b, c) => a + b + c

const currySuma = curry(suma)
/*
posibles llamadas
currySuma(a)(b)(c),
currySuma(a)(b,c),
currySuma(a,b)(c),
currySuma(a,b,c)
*/

currySum(1) // (b, c) => 1 + a + b o (b) => (c) => 1 + a + b
currySum(5)(10) // (c) => 5 + 10 + b
currySum(5, 10) // (c) => 5 + 10 + b
currySuma(5)(10)(20) // 35
currySuma(5, 10)(20) // 35
currySuma(5)(10, 20) // 35

const dividirPor = curry((a, b) => b / a)
const multiplicarPor = curry((a, b) => a * b)

const dividePorDos = dividePor(2)
divideByTwo(10) // devuelve 5

const multiplicarPorCinco = multiplicarPor(5)
multiplyByFive(10) // devuelve 50

Tubería/Composición

Mediante la composición, es posible pasar datos de una función a otra. Es importante que las funciones tomen el mismo número de argumentos. La diferencia entre pipe y compose es que la primera ejecuta la función de la primera a la última, y compose las llama desde el final.

Declaración

const pipe = (...fns) => (valor, ...args) =>
fns.reduce((v, f, i) =>
i === 0
? f(v, ...args)
: f(v),
valor);

const compose = (...fns) => (valor, ...args) =>
fns.reduceRight((v, f, i) =>
i === (fns.length - 1)
? f(v, ...args)
: f(v),
valor);
const pipe = (...fns) => (valor, ...args) =>
fns.reduce((v, f, i) =>
i === 0
? f(v, ...args)
: f(v),
valor);

const compose = (...fns) => (valor, ...args) =>
fns.reduceRight((v, f, i) =>
i === (fns.length - 1)
? f(v, ...args)
: f(v),
valor);

Ejemplo imperativo

const seno = (val) => Math.sin(val * Math.PI / 180) // no legible
 sine(90) // devuelve 1

Ejemplo declarativo

const seno = pipe(
multiplyBy(Math.PI) // ↓ val * Math.PI
dividePor(180), // ↓ val * Math.PI / 180
Math.sin, // ↓ Math.sin(val * Math.PI / 180)
)

const seno = componer(
Math.sin, // ↑ Math.sin(val * Math.PI / 180)
dividePor(180), // ↑ val * Math.PI / 180
multiplyBy(Math.PI) // ↑ val * Math.PI
)

seno(90) // devuelve 1

Horquilla

El combinador de horquilla es útil en situaciones en las que se desea procesar un valor de dos formas y combinar el resultado.

Declaración

const fork = (join, fn1, fn2) => (valor) => join(fn1(valor), fn2(valor));

Ejemplo

const longitud = (array) => array.longitud
const suma = (array) => array.reduce((a, b) => a + b, 0)
const divide = (a, b) => a / b

const media aritmética = fork(divide, suma, longitud)

arithmeticAverage([5, 3, 2, 8, 4, 2]) // devuelve 4

Alternancia

Este combinador toma dos funciones y devuelve el resultado de la primera si es verdadero. En caso contrario, devuelve el resultado de la segunda función.

Declaración

const alt = (fn, orFn) => (valor) => fn(valor) || orFn(valor)

Ejemplo

const usuarios = [{
uuid: '123e4567-e89b-12d3-a456-426655440000',
name: 'William'
}]

const findUser = ({ uuid: searchesUuid }) =>
users.find(({ uuid }) => uuid === searchesUuid)

const newUser = datos => ({ ...datos, uuid: uuid() // crear nuevo uuid })

const findOrCreate = alt(findUser, newUser)

findOrCreate({ uuid: '123e4567-e89b-12d3-a456-426655440000' }) // devuelve el objeto William
findOrCreate({ name: 'John' }) // devuelve el objeto John con el nuevo uuid

Secuencia

Acepta muchas funciones independientes y pasa el mismo parámetro a cada una de ellas. Normalmente, estas funciones no devuelven ningún valor.

Declaración

const seq = (...fns) => (val) => fns.forEach(fn => fn(val))

Ejemplo

const appendUser = (id) => ({ name }) => {
document.getElementById(id).innerHTML = nombre
}

const printUserContact = pipe(
findOrCreate, // devuelve el usuario
seq(
appendUser('#contact'), // usuario => void
console.log, // usuario => void
onContactUpdate // usuario => void
)
)

printUserContact(userData)
bandera de cooperación
es_ESSpanish