Inleiding tot combinatoren
Combinatoren zijn functies op een hoger niveau waarmee je functies, variabelen of andere combinatoren kunt combineren. Meestal bevatten ze geen declaraties van hun eigen variabelen of bedrijfslogica. Ze zijn de enige die de besturing doorspoelen in een functieprogramma.
In dit artikel zal ik proberen er een paar te behandelen:
- Tik op
- Curry
- Pijpen/Componeren
- Vork
- Afwisseling
- Volgorde
Tik op
Een combinator is erg handig voor functies die niets teruggeven. Het neemt de functie waar de parameter naartoe gaat en geeft deze terug.
Verklaring
const tap = (fn) => (value) => {
fn(waarde);
waarde retourneren;
};
Imperatief voorbeeld
const [items, setItems] = useState() => [])
axios
.get('http://localhost')
.then({data } => {
setItems(data)
console.log(gegevens)
onLoadData(data)
}).then(...) // geeft ongedefinieerd terug - de gegevens in de belofte zijn gewijzigd
Declaratief voorbeeld
const [items, setItems] = useState() => [])
axios
.get('http://localhost')
.then(({data }) => data)
.then(tap(setItems)) // (data) => { setItems(data); return data }
.then(tap(console.log)) // een dan = een functie = een verantwoordelijkheid
.then(tap(onLoadData))
.then(...) // nog steeds toegang tot oorspronkelijke gegevens
// makkelijk te onderhouden open/close principe
Curry
Het splitst de argumenten van een functie en maakt het mogelijk om ze opeenvolgend aan te roepen.
Verklaring
const curry = (fn) => {
const curry = (...args) => {
if (fn.length !.== args.length){
return curied.bind(null, ...args)
}
return fn(...args);
};
return curied
};
Voorbeeld
const curry = (fn) => {
const curry = (...args) => {
if (fn.length !.== args.length){
return curied.bind(null, ...args)
}
return fn(...args);
};
return curied
};
const som = (a, b, c) => a + b + c
const currySum = curry(som)
/*
mogelijke oproepen
currySum(a)(b)(c),
currySum(a)(b,c),
currySum(a,b)(c),
currySum(a,b,c)
*/
currySum(1) // (b, c) => 1 + a + b of (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 delerBy = curry((a, b) => b / a)
const multiplyBy = curry((a, b) => a * b)
const divideByTwo = delenDoor(2)
divideByTwo(10) // geeft 5 terug
const multiplyByFive = multiplyBy(5)
multiplyByFive(10) // geeft 50 terug
Pijpen/Componeren
Door compositie is het mogelijk om gegevens door te geven van de ene functie naar de andere. Het is belangrijk dat de functies hetzelfde aantal argumenten aannemen. Het verschil tussen pipe en compose is dat de eerste de functie van de eerste tot de laatste uitvoert, en compose ze vanaf het einde aanroept.
Verklaring
const pipe = (...fns) => (waarde, ...args) =>
fns.reduce((v, f, i) =>
i === 0
? f(v, ...args)
: f(v),
waarde);
const compose = (...fns) => (value, ...args) =>
fns.reduceRight((v, f, i) =>
i === (fns.length - 1)
? f(v, ...args)
: f(v),
waarde);
const pipe = (...fns) => (waarde, ...args) =>
fns.reduce((v, f, i) =>
i === 0
? f(v, ...args)
: f(v),
waarde);
const compose = (...fns) => (value, ...args) =>
fns.reduceRight((v, f, i) =>
i === (fns.length - 1)
? f(v, ...args)
: f(v),
waarde);
Imperatief voorbeeld
const sinus = (val) => Math.sin(val * Math.PI / 180) // niet leesbaar
sinus(90) // geeft 1 terug
Declaratief voorbeeld
const sinus = pijp(
vermenigvuldig(Math.PI) // ↓ waarde * Math.PI
delenDoor(180), // ↓ waarde * Math.PI / 180
Math.sin, // ↓ Math.sin(val * Math.PI / 180)
)
const sinus = samenstellen(
Math.sin, // ↑ Math.sin(val * Math.PI / 180)
delenDoor(180), // ↑ waarde * Math.PI / 180
multiplyBy(Math.PI) // ↑ waarde * Math.PI
)
sinus(90) // geeft 1 terug
Vork
De fork combiner is handig in situaties waarin je een waarde op twee manieren wilt verwerken en het resultaat wilt combineren.
Verklaring
const fork = (join, fn1, fn2) => (waarde) => join(fn1(waarde), fn2(waarde));
Voorbeeld
const lengte = (array) => array.lengte
const som = (array) => array.reduce((a, b) => a + b, 0)
const delen = (a, b) => a / b
const rekenkundig gemiddelde = fork(delen, som, lengte)
arithmeticAverage([5, 3, 2, 8, 4, 2]) // geeft 4 terug
Afwisseling
Deze combinator neemt twee functies en geeft het resultaat van de eerste terug als deze waar is. Anders geeft hij het resultaat van de tweede functie terug.
Verklaring
const alt = (fn, orFn) => (waarde) => fn(waarde) || orFn(waarde)
Voorbeeld
const gebruikers = [{
uuid: '123e4567-e89b-12d3-a456-426655440000',
naam: 'William
}]
const findUser = ({ uuid: zoekopdrachtenUuid }) =>
users.find(({ uuid }) => uuid === zoektUuid)
const newUser = data => ({ ...data, uuid: uuid() // nieuwe uuid maken })
const findOrCreate = alt(findUser, newUser)
findOrCreate({ uuid: '123e4567-e89b-12d3-a456-426655440000' }) // retourneert William-object
findOrCreate({naam: 'John' }) // retourneert John object met nieuwe uuid
Volgorde
Het accepteert veel onafhankelijke functies en geeft dezelfde parameter door aan elk van hen. Meestal geven deze functies geen waarde terug.
Verklaring
const seq = (...fns) => (val) => fns.forEach(fn => fn(val))
Voorbeeld
const appendUser = (id) => ({naam }) => {
document.getElementById(id).innerHTML = naam
}
const printUserContact = pipe(
findOrCreate, // retourneert gebruiker
seq(
appendUser('#contact'), // gebruiker => void
console.log, // gebruiker => void
onContactUpdate // gebruiker => void
)
)
printUserContact(userData)
