Sissejuhatus kombinaatoritesse
Kombinaatorid on kõrgema taseme funktsioonid, mis võimaldavad kombineerida funktsioone, muutujaid või teisi kombinaatoreid. Tavaliselt ei sisalda need oma muutujate või äriloogika deklaratsioone. Nad on ainsad, mis loputavad kontrolli funktsiooniprogrammis.
Käesolevas artiklis püüan käsitleda neist mõningaid:
- Tap
- Currying
- Toru/koosseis
- Kahvel
- Vaheldumine
- Järjestus
Tap
Kombinaator on väga kasulik funktsioonide puhul, mis ei tagasta midagi. See võtab funktsiooni, kuhu parameeter läheb, ja seejärel tagastatakse see.
Deklaratsioon
const tap = (fn) => (value) => {
fn(value);
return value;
};
Imperatiivne näide
const [items, setItems] = useState(() => [])
axios
.get('http://localhost')
.then({ data } => {
setItems(data)
console.log(data)
onLoadData(data)
}).then(....) // tagastab undefined - andmed lubaduses on muudetud.
Deklaratiivne näide
const [items, setItems] = useState(() => [])
axios
.get('http://localhost')
.then(({ data }) => data)
.then(tap(setItems)) // (data) => { setItems(data); return data }
.then(tap(console.log)) // üks then = üks funktsioon = üks vastutus
.then(tap(onLoadData))
.then(....) // ikkagi juurdepääs algsetele andmetele
// lihtne säilitada avatud/suletud põhimõtet
Currying
See jagab funktsiooni argumendid ja võimaldab neid järjestikku välja kutsuda.
Deklaratsioon
const curry = (fn) => {
const curried = (...args) => {
if (fn.length !== args.length){
return curried.bind(null, ...args)
}
return fn(...args);
};
return curried
};
Näide
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)
/*
võimalikud kutsed
currySum(a)(b)(c),
currySum(a)(b,c),
currySum(a,b)(c),
currySum(a,b,c)
*/
currySum(1) // (b, c) => 1 + a + b või (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) // tagastab 5
const multiplyByFive = multiplyBy(5)
multiplyByFive(10) // tagastab 50
Toru/koosseis
Kompositsiooni abil on võimalik andmeid ühest funktsioonist teise edastada. Oluline on, et funktsioonid võtaksid sama arvu argumente. Pipe ja compose erinevus seisneb selles, et esimene täidab funktsiooni esimesest kuni viimaseni ja compose kutsub neid lõpust alates.
Deklaratsioon
const pipe = (...fns) => (value, ...args) =>
fns.reduce((v, f, i) =>
i === 0
? f(v, ...args)
: f(v),
value);
const compose = (...fns) => (value, ...args) =>
fns.reduceRight((v, f, i) =>
i === (fns.length - 1)
? f(v, ...args)
: f(v),
value);
const pipe = (...fns) => (value, ...args) =>
fns.reduce((v, f, i) =>
i === 0
? f(v, ...args)
: f(v),
value);
const compose = (...fns) => (value, ...args) =>
fns.reduceRight((v, f, i) =>
i === (fns.length - 1)
? f(v, ...args)
: f(v),
value);
Imperatiivne näide
const sine = (val) => Math.sin(val * Math.PI / 180) // ei ole loetav
sine(90) // tagastab 1
Deklaratiivne näide
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
)
sine(90) // tagastab 1
Kahvel
Kahvlikombinaator on kasulik olukordades, kus soovite töödelda väärtust kahel viisil ja kombineerida tulemust.
Deklaratsioon
const fork = (join, fn1, fn2) => (value) => join(fn1(value), fn2(value));
Näide
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]) // tagastab 4
Vaheldumine
See kombinaator võtab kaks funktsiooni ja tagastab esimese tulemuse, kui see on tõene. Vastasel juhul tagastab ta teise funktsiooni tulemuse.
Deklaratsioon
const alt = (fn, orFn) => (value) => fn(value) || orFn(value)
Näide
const users = [{
uuid: '123e4567-e89b-12d3-a456-426655440000',
name: 'William'
}]
const findUser = ({ uuid: searchesUuid }) =>
users.find(({ uuid }) => uuid === searchesUuid)
const newUser = data => ({ ...data, uuid: uuid() // luua uus uuid })
const findOrCreate = alt(findUser, newUser)
findOrCreate({ uuid: '123e4567-e89b-12d3-a456-426655440000' }) // tagastab William-objekti
findOrCreate({ name: 'John' }) // tagastab John objekti uue uuidiga.
Järjestus
See võtab vastu mitu sõltumatut funktsiooni ja annab igale neist sama parameetri. Tavaliselt ei tagasta need funktsioonid mingit väärtust.
Deklaratsioon
const seq = (...fns) => (val) => fns.forEach(fn => fn(val))
Näide
const appendUser = (id) => ({ name }) => {
document.getElementById(id).innerHTML = nimi
}
const printUserContact = pipe(
findOrCreate, // tagastab kasutaja
seq(
appendUser('#contact'), // user => void
console.log, // kasutaja => void
onContactUpdate // user => void
)
)
printUserContact(userData)
