Introduktion til kombinatorer
Kombinatorer er funktioner på et højere niveau, som gør det muligt at kombinere funktioner, variabler eller andre kombinatorer. Normalt indeholder de ikke erklæringer af deres egne variabler eller forretningslogik. De er de eneste, der kan skylle kontrollen i et funktionsprogram.
I denne artikel vil jeg forsøge at dække et par af dem:
- Tap
- Karry
- Pipe/Compose
- Gaffel
- Vekselvirkning
- Sekvens
Tap
En kombinator er meget nyttig til funktioner, der ikke returnerer noget. Den tager den funktion, som parameteren går til, og så returneres den.
Erklæring
const tap = (fn) => (værdi) => {
fn(værdi);
returnerer værdi;
};
Imperativt eksempel
const [items, setItems] = useState(() => [])
axios
.get('http://localhost')
.then({ data } => {
setItems(data)
console.log(data)
onLoadData(data)
}).then(...) // returnerer udefineret - dataene i løftet er blevet ændret
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 funktion = et ansvar
.then(tap(onLoadData))
.then(...) // stadig adgang til oprindelige data
// nemt at opretholde åbne/lukke-princippet
Karry
Den opdeler argumenterne i en funktion og gør det muligt at kalde dem sekventielt.
Erklæring
const curry = (fn) => {
const curried = (...args) => {
if (fn.length !.== args.length){
return curried.bind(null, ...args)
}
return fn(...args);
};
returner curried
};
Eksempel
const curry = (fn) => {
const curried = (...args) => {
if (fn.length !.== args.length){
return curried.bind(null, ...args)
}
return fn(...args);
};
returner curried
};
const sum = (a, b, c) => a + b + c
const currySum = curry(sum)
/*
mulige kald
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
Gennem komposition er det muligt at sende data fra en funktion til en anden. Det er vigtigt, at funktionerne tager det samme antal argumenter. Forskellen mellem pipe og compose er, at den første udfører funktionen fra først til sidst, og compose kalder dem fra slutningen.
Erklæring
const pipe = (...fns) => (value, ...args) =>
fns.reduce((v, f, i) =>
i === 0
? f(v, ...args)
: f(v),
værdi);
const compose = (...fns) => (value, ...args) =>
fns.reduceRight((v, f, i) =>
i === (fns.length - 1)
? f(v, ...args)
: f(v),
værdi);
const pipe = (...fns) => (value, ...args) =>
fns.reduce((v, f, i) =>
i === 0
? f(v, ...args)
: f(v),
værdi);
const compose = (...fns) => (value, ...args) =>
fns.reduceRight((v, f, i) =>
i === (fns.length - 1)
? f(v, ...args)
: f(v),
værdi);
Imperativt eksempel
const sine = (val) => Math.sin(val * Math.PI / 180) // ikke læsbar
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 sine = 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 situationer, hvor man vil behandle en værdi på to måder og kombinere resultatet.
Erklæring
const fork = (join, fn1, fn2) => (value) => join(fn1(value), fn2(value));
Eksempel
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]) // returnerer 4
Vekselvirkning
Denne kombinator tager to funktioner og returnerer resultatet af den første, hvis den er sand. Ellers returnerer den resultatet af den anden funktion.
Erklæring
const alt = (fn, orFn) => (værdi) => fn(værdi) || orFn(værdi)
Eksempel
const users = [{
uuid: '123e4567-e89b-12d3-a456-426655440000',
navn: 'William'
}]
const findUser = ({ uuid: searchesUuid }) =>
users.find(({ uuid }) => uuid === searchesUuid)
const newUser = data => ({ ...data, uuid: uuid() // opret ny uuid })
const findOrCreate = alt(findUser, newUser)
findOrCreate({ uuid: '123e4567-e89b-12d3-a456-426655440000' }) // returnerer William-objektet
findOrCreate({ name: 'John' }) // returnerer John-objekt med nyt uuid
Sekvens
Den accepterer mange uafhængige funktioner og sender den samme parameter til hver af dem. Disse funktioner returnerer typisk ikke nogen værdi.
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 bruger
seq(
appendUser('#contact'), // user => void
console.log, // bruger => void
onContactUpdate // bruger => void
)
)
printUserContact(brugerdata)
