Introduksjon til kombinatorer
Kombinatorer er funksjoner på høyere nivå som lar deg kombinere funksjoner, variabler eller andre kombinatorer. Vanligvis inneholder de ikke deklarasjoner av egne variabler eller forretningslogikk. De er de eneste som kan spyle kontrollen i et funksjonsprogram.
I denne artikkelen vil jeg forsøke å ta for meg noen av dem:
- Trykk på
- Currying
- Pipe/Compose
- Gaffel
- Alternasjon
- Sekvens
Trykk på
En kombinator er veldig nyttig for funksjoner som ikke returnerer noe. Den tar funksjonen som parameteren går til, og deretter returneres den.
Erklæring
const tap = (fn) => (verdi) => {
fn(verdi);
returnere verdi;
};
Imperativt eksempel
const [items, setItems] = useState(() => [])
axios
.get('http://localhost')
.then({ data } => {
setItems(data)
console.log(data)
onLoadData(data)
}).then(...) // returnerer udefinert - dataene i løftet har blitt endret
Deklarativt eksempel
const [items, setItems] = useState(() => [])
axios
.get('http://localhost')
.then(({data }) => data)
.then(tap(setItems)) // (data) => { setItems(data); return data }
.then(tap(console.log))) // en then = en funksjon = et ansvar
.then(tap(onLoadData))
.then(...) // fortsatt tilgang til opprinnelige data
// enkelt å opprettholde åpne/lukke-prinsippet
Currying
Den deler opp argumentene til en funksjon og gjør det mulig å kalle dem sekvensielt.
Erklæring
const curry = (fn) => {
const curried = (...args) => {
if (fn.length !.== args.length){
return curried.bind(null, ...args)
}
return fn(...args);
};
return curried
};
Eksempel
const curry = (fn) => {
const curried = (...args) => {
if (fn.length !.== args.length){
return curried.bind(null, ...args)
}
return fn(...args);
};
return curried
};
const sum = (a, b, c) => a + b + c
const currySum = curry(sum)
/*
mulige 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) // returnerer 5
const multiplyByFive = multiplyBy(5)
multiplyByFive(10) // returnerer 50
Pipe/Compose
Gjennom komposisjon er det mulig å sende data fra en funksjon til en annen. Det er viktig at funksjonene tar samme antall argumenter. Forskjellen mellom pipe og compose er at den første utfører funksjonen fra først til sist, mens compose kaller dem fra slutten.
Erklæring
const pipe = (...fns) => (value, ...args) =>
fns.reduce((v, f, i) => (v, f, i)
i === 0
? f(v, ...args)
: f(v),
value);
const compose = (...fns) => (value, ...args) =>
fns.reduceRight((v, f, i) => (v, f, i) => (fns.length)
i === (fns.lengde - 1)
? f(v, ...args)
: f(v),
verdi);
const pipe = (...fns) => (value, ...args) =>
fns.reduce((v, f, i) => (v, f, i)
i === 0
? f(v, ...args)
: f(v),
value);
const compose = (...fns) => (value, ...args) =>
fns.reduceRight((v, f, i) => (v, f, i) => (fns.length)
i === (fns.lengde - 1)
? f(v, ...args)
: f(v),
verdi);
Imperativt eksempel
const sinus = (val) => Math.sin(val * Math.PI / 180) // ikke lesbar
sine(90) // returnerer 1
Deklarativt eksempel
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) // returnerer 1
Gaffel
Gaffelkombinatoren er nyttig i situasjoner der du ønsker å behandle en verdi på to måter og kombinere resultatet.
Erklæring
const fork = (join, fn1, fn2) => (value) => join(fn1(value), fn2(value));
Eksempel
const length = (matrise) => matrise.lengde
const sum = (matrise) => matrise.reduce((a, b) => a + b, 0)
const divide = (a, b) => a / b
const arithmeticAverage = fork(divide, sum, lengde)
arithmeticAverage([5, 3, 2, 8, 4, 2]) // returnerer 4
Alternasjon
Denne kombinatoren tar to funksjoner og returnerer resultatet av den første hvis det er sant. I motsatt fall returnerer den resultatet av den andre funksjonen.
Erklæring
const alt = (fn, orFn) => (value) => fn(value) || orFn(value)
Eksempel
const users = [{
uuid: '123e4567-e89b-12d3-a456-426655440000',
navn: 'William'
}]
const findUser = ({ uuid: searchesUuid }) => ({ uuid: searchesUuid }) =>
users.find(({ uuid }) => uuid === searchesUuid)
const newUser = data => ({ ...data, uuid: uuid() // opprett ny uuid })
const findOrCreate = alt(findUser, newUser)
findOrCreate({ uuid: '123e4567-e89b-12d3-a456-426655440000' }) // returnerer William-objekt
findOrCreate({ name: 'John' }) // returnerer John-objektet med ny uuid
Sekvens
Den aksepterer mange uavhengige funksjoner og sender den samme parameteren til hver av dem. Vanligvis returnerer ikke disse funksjonene noen verdi.
Erklæring
const seq = (...fns) => (val) => fns.forEach(fn => fn(val))
Eksempel
const appendUser = (id) => ({ name }) => {
document.getElementById(id).innerHTML = navn
}
const printUserContact = pipe(
findOrCreate, // returnerer bruker
seq(
appendUser('#contact'), // bruker => void
console.log, // bruker => void
onContactUpdate // bruker => void
)
)
printUserContact(userData)
