Introduktion till kombinatorer
Kombinatorer är funktioner på högre nivå som gör att du kan kombinera funktioner, variabler eller andra kombinatorer. Vanligtvis innehåller de inte deklarationer av egna variabler eller affärslogik. De är de enda som kan spola kontrollen i ett funktionsprogram.
I den här artikeln ska jag försöka ta upp några av dem:
- Kran
- Currying
- Rörledning/Compose
- Gaffel
- Växling
- Sekvens
Kran
En kombinator är mycket användbar för funktioner som inte returnerar någonting. Den tar den funktion som parametern går till och sedan returneras den.
Förklaring
const tap = (fn) => (värde) => {
fn(värde);
returnerar värde;
};
Imperativt exempel
const [objekt, setItems] = useState(() => [])
axios
.get('http://localhost')
.then({ data } => {
setItems(data)
console.log(data)
onLoadData(data)
}).then(...) // returnerar odefinierat - data i löftet har ändrats
Deklarativt exempel
const [objekt, setItems] = useState(() => [])
axios
.get('http://localhost')
.then(({data }) => data)
.then(tap(setItems)) // (data) => { setItems(data); returnera data }
.then(tap(console.log)) // en då = en funktion = ett ansvar
.then(tap(onLoadData))
.then(...) // fortfarande tillgång till originaldata
// lätt att upprätthålla öppna/stäng-principen
Currying
Den delar upp argumenten i en funktion och gör det möjligt att anropa dem sekventiellt.
Förklaring
const curry = (fn) => {
const curried = (...args) => {
if (fn.length !== args.length){
return curried.bind(null, ...args)
}
return fn(...args);
};
returnera curried
};
Exempel
const curry = (fn) => {
const curried = (...args) => {
if (fn.length !== args.length){
return curried.bind(null, ...args)
}
return fn(...args);
};
returnera curried
};
const sum = (a, b, c) => a + b + c
const currySum = curry(sum)
/*
möjliga anrop
currySum(a)(b)(c),
currySum(a)(b,c),
currySum(a,b)(c),
currySum(a,b,c)
*/
currySum(1) // (b, c) => 1 + a + b eller (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) // returnerar 5
const multiplyByFive = multiplyBy(5)
multiplyByFive(10) // returnerar 50
Rörledning/Compose
Genom komposition är det möjligt att skicka data från en funktion till en annan. Det är viktigt att funktionerna tar samma antal argument. Skillnaden mellan pipe och compose är att den första exekverar funktionen från början till slut, medan compose anropar dem från slutet.
Förklaring
const pipe = (...fns) => (värde, ...args) =>
fns.reduce((v, f, i) =>
i === 0
? f(v, ...args)
: f(v),
värde);
const compose = (...fns) => (värde, ...args) =>
fns.reduceRight((v, f, i) => (v, ... args)
i === (fns.längd - 1)
? f(v, ...args)
: f(v),
värde);
const pipe = (...fns) => (värde, ...args) =>
fns.reduce((v, f, i) =>
i === 0
? f(v, ...args)
: f(v),
värde);
const compose = (...fns) => (värde, ...args) =>
fns.reduceRight((v, f, i) => (v, ... args)
i === (fns.längd - 1)
? f(v, ...args)
: f(v),
värde);
Imperativt exempel
const sinus = (val) => Math.sin(val * Math.PI / 180) // inte läsbar
sinus(90) // returnerar 1
Deklarativt exempel
const sinus = pipe(
multiplyBy(Math.PI) // ↓ val * Math.PI
divideBy(180), // ↓ val * Math.PI / 180
Math.sin, // ↓ Math.sin(val * Math.PI / 180)
)
const sinus = compose(
Math.sin, // ↑ Math.sin(val * Math.PI / 180)
divideBy(180), // ↑ val * Math.PI / 180
multiplyBy(Math.PI) // ↑ val * Math.PI
)
sinus(90) // returnerar 1
Gaffel
Fork combiner är användbar i situationer där du vill bearbeta ett värde på två sätt och kombinera resultatet.
Förklaring
const fork = (join, fn1, fn2) => (värde) => join(fn1(värde), fn2(värde));
Exempel
const length = (matris) => matris.längd
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]) // returnerar 4
Växling
Denna kombinator tar två funktioner och returnerar resultatet av den första om det är sant. Annars returnerar den resultatet av den andra funktionen.
Förklaring
const alt = (fn, orFn) => (värde) => fn(värde) || orFn(värde)
Exempel
const användare = [{
uuid: '123e4567-e89b-12d3-a456-426655440000',
namn: 'William'
}]
const findUser = ({ uuid: searchesUuid }) => ({ uuid: searchesUuid })
users.find(({ uuid }) => uuid === searchesUuid)
const newUser = data => ({ ...data, uuid: uuid() // skapa ny uuid })
const findOrCreate = alt(findUser, newUser)
findOrCreate({ uuid: '123e4567-e89b-12d3-a456-426655440000' }) // returnerar William-objekt
findOrCreate({ name: 'John' }) // returnerar John-objekt med ny uuid
Sekvens
Den accepterar många oberoende funktioner och skickar samma parameter till var och en av dem. Vanligtvis returnerar dessa funktioner inte något värde.
Förklaring
const seq = (...fns) => (val) => fns.forEach(fn => fn(val))
Exempel
const appendUser = (id) => ({ namn }) => {
document.getElementById(id).innerHTML = namn
}
const printUserContact = pipe(
findOrCreate, // returnerar användare
seq(
appendUser('#contact'), // användare => void
console.log, // användare => void
onContactUpdate // användare => void
)
)
printUserContact(användardata)
