Introduzione ai combinatori

I combinatori sono funzioni di livello superiore che consentono di combinare funzioni, variabili o altri combinatori. Di solito, non contengono dichiarazioni delle proprie variabili o della logica aziendale. Sono gli unici che permettono di controllare il controllo in un programma di funzioni.

In questo articolo cercherò di trattarne alcuni:

Rubinetto

Un combinatore è molto utile per le funzioni che non restituiscono nulla. Prende la funzione a cui va il parametro e poi la restituisce.

Dichiarazione

const tap = (fn) => (valore) => {
fn(valore);
restituisce il valore;
};

Esempio di imperativo

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

axios
.get('http://localhost')
.then({dati } => {
setItems(dati)
console.log(dati)
onLoadData(dati)
}).then(...) // restituisce undefined - i dati nella promessa sono stati modificati

Esempio dichiarativo

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

axios
.get('http://localhost')
.then(({dati }) => dati)
.then(tap(setItems)) // (data) => { setItems(data); return data }
.then(tap(console.log)) // un then = una funzione = una responsabilità
.then(tap(onLoadData))
.then(...) // ancora accesso ai dati originali
// facile mantenere il principio di apertura/chiusura

Arricciatura

Divide gli argomenti di una funzione e permette di chiamarli in sequenza.

Dichiarazione

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

restituire curried

};


Esempio

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

restituire curried

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

const currySum = curry(sum)
/*
possibili chiamate
currySum(a)(b)(c),
currySum(a)(b,c),
currySum(a,b)(c),
currySum(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
currySum(5)(10)(20) // 35
currySum(5, 10)(20) // 35
currySum(5)(10, 20) // 35

const divideBy = curry((a, b) => b / a)
const multiplyBy = curry((a, b) => a * b)

const divideByTwo = divideBy(2)
divideByTwo(10) // restituisce 5

const multiplyByFive = multiplyBy(5)
multiplyByFive(10) // restituisce 50

Tubo/Comporre

Attraverso la composizione, è possibile passare i dati da una funzione all'altra. È importante che le funzioni accettino lo stesso numero di argomenti. La differenza tra pipe e compose è che la prima esegue le funzioni dalla prima all'ultima, mentre compose le chiama dalla fine.

Dichiarazione

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

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

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

Esempio di imperativo

const seno = (val) => Math.sin(val * Math.PI / 180) // non leggibile
 sine(90) // restituisce 1

Esempio dichiarativo

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

const sine = compose(
Math.sin, // ↑ Math.sin(val * Math.PI / 180)
divideBy(180), // ↑ val * Math.PI / 180
multiplyBy(Math.PI) // ↑ val * Math.PI
)

seno(90) // restituisce 1

Forcella

Il combinatore a forcella è utile nelle situazioni in cui si desidera elaborare un valore in due modi e combinare il risultato.

Dichiarazione

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

Esempio

const length = (array) => array.length
const sum = (array) => array.reduce((a, b) => a + b, 0)
const divide = (a, b) => a / b

const arithmeticAverage = fork(divide, sum, length)

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

Alternanza

Questo combinatore accetta due funzioni e restituisce il risultato della prima se è vero. Altrimenti, restituisce il risultato della seconda funzione.

Dichiarazione

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

Esempio

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

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

const newUser = data => ({ ...data, uuid: uuid() // crea un nuovo uuid })

const findOrCreate = alt(findUser, newUser)

findOrCreate({ uuid: '123e4567-e89b-12d3-a456-426655440000' }) // restituisce l'oggetto William
findOrCreate({nome: 'John' }) // restituisce l'oggetto John con il nuovo uuid

Sequenza

Accetta molte funzioni indipendenti e passa lo stesso parametro a ciascuna di esse. In genere, queste funzioni non restituiscono alcun valore.

Dichiarazione

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

Esempio

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

const printUserContact = pipe(
findOrCreate, // restituisce l'utente
seq(
appendUser('#contact'), // utente => void
console.log, // utente => void
onContactUpdate // utente => void
)
)

printUserContact(userData)
banner di cooperazione
it_ITItalian