reduce(
add,
map(p => p.price,
filter(p => p.price < 20000, products)))
위 코드를 읽기 위해서는 왼쪽에서 오른쪽으로, 아래에서 위로 읽어야 하므로 가독성 면에서 단점이 많다.
이런 코드를 값으로 만들어서 가독성을 높이기 위한 방법으로 go와 pipe를 만들 수 있다.
go함수는 여러 인자를 받아서 하나의 값으로 반환하는 함수이다.
go( 1,
(a) => a+10,
(a) => a + 100,
log
)
를 했을때 111이 출력되도록 go함수를 만들어보자
go함수는 함수를 차례대로 실행하는 것이므로 reduce를 사용해서 만들 수 있다.
const go = (...arg) => {
return reduce((a, f) => f(a) , arg)
}
...arg 에는 [ 1, (a) => a+10, (a) => a + 100, log]
가 리스트로 들어가게 된다 .
reduce는 이 리스트 중 첫번째 값이 acc로 시작되므로, reduce의 마지막 인자 arg를 그대로 넣어준다.
첫번째 인자에는 reduce를 하면서 실행될 함수를 넣어주자.
f는 리스트에 들어가있는 함수들이 차례대로 실행되고, a는 실행된 함수의 값이 들어가게 된다.
go를 사용하면 위에서 20000원 이하 상품의 총 합계는 이렇게 출력할 수 있다.
go(
products,
products => filter(p => p.price < 20000, products),
products => map(p => p.price, products),
prices => reduce((a,b)=>a+b, prices),
console.log);
pipe함수는 여러 함수를 인자로 받지만 go와 다른 점은 함수를 리턴한다.
내부적으로는 go를 사용한다.
const pipe = (f,...rest) => (...args) => {
return go(f(...args), ...rest)
}
const f = pipe(
(a,b) => a+b,
(a) => a+10,
(a) => a+100,
)
log(f) // (...args) => {return go(f(...args),...rest)}
log(f(1,0)) // 111
curry는 인자의 개수에 따라서 함수의 실행여부를 결정한다.
const curry = (f) => (a, ...rest) => {
if (rest.length) {
return f(a,...rest);
} else {
return (...rest) => f(a, ...rest)
}
}
const mult = curry((a,b) => a+b);
log(mult(3))// (...rest) => f(a, ...rest)
// 인자가 하나
const mult3 = mult(3);
log(mult(4))// 7
log(mult(3)(4)) // 7
log(mult(3)(5)) // 8
log(mult(3)(6)) // 9
// 인자가 두개
curry는 함수를 리턴하는 함수이다.
리턴된 함수의 인자가 2개 이상인 경우 (= length가 있을 때) 리턴 된 함수를 인자와 함께 실행한다.
만약 인자가 하나인 경우엔 나머지 인자를 함수에 넣어서 리턴하고, 미리 만들어진 인자와 함께 실행하게 된다.
map, reduce, filter에 curry 를 적용하면 인자의 수에 따라 인자가 하나만 있으면 이후 인자를 기다리게 되는 함수가 리턴된다.
const map = curry((f, iter) => {
let res = [];
for (const a of iter) {
res.push(f(a))
}
return res;
});
const filter = curry((f, iter) => {
let res = [];
for (const a of iter) {
if (f(a)) {
res.push(a);
}
}
return res;
});
const reduce = curry((f, acc, iter) => {
if (!iter){
const iter = acc[Symbol.iterator]();
acc = iter.next().value;
}
for (const a of iter ) {
acc = f(acc, a);
}
return acc;
});
go(
products,
products => filter(p => p.price < 20000), (products)),
products => map((p => p.price), (products)),
prices => reduce(((a,b)=>a+b), (prices)),
console.log);
그러면 위와 같이 변경할 수 있다.
예를 들어 fiiter에서는 먼저 (p => p.price < 20000) 함수를 받고 이후에 (products)인자가 올때 까지 기다리면서 함수가 실행된다.
이어서 filter(p => p.price < 20000), (products)) 것을 평가한 결과가 products가 되는 것이므로, 아래처럼 변경하여 코드를 간결하게 개선할 수 있다.
go(
products,
filter(p => p.price < 20000),
map(p => p.price),
reduce((a, b) => a + b),
console.log);
go(
products,
filter(p => p.price < 20000),
map(p => p.price),
reduce((a, b) => a + b),
console.log);
go(
products,
filter(p => p.price >= 20000),
map(p => p.price),
reduce((a, b) => a + b),
console.log);
위 두 코드에서 중복을 제거해보자.
const add = (a,b) => a +b
const total_price = pipe(
map(p => p.price),
reduce(add),
)
pipe를 통해 중복되는 함수를 추출했다.
go(
products,
filter(p => p.price < 20000),
total_price
console.log);
go(
products,
filter(p => p.price >= 20000),
total_price
console.log);
filter부분을 pipe와 함수로 합쳐서 함수를 만들 수 있다.
const base_total_price = (predi) => {
return pipe(
filter(predi),
total_price)
}
go(
products,
base_total_price(p => p.price < 20000),
console.log);
go(
products,
base_total_price(p => p.price >= 20000),
console.log);
최종적으로 이렇게 함수를 개선할 수 있다