똑같은 삽질은 2번 하지 말자
Javascript ES6+ 에서 함수형 프로그래밍(go, pipe,curry) 본문
좋은 코드란 ?
누구나 이해하기 쉽고, 직관적인 코드들이다.
그런 직관적이 코드들을 구현하기위한 과정을 적어보자
이하의 map, filter, reduce (표준 라이브러리들과 기능들은 같은데 인자로 iterable을 받는다는 점이 틀리다)
const map = (f, iter) => {
let res = [];
for (const a of iter) {
res.push(f(a));
}
return res;
};
const filter = (f, iter) => {
let res = [];
for (const a of iter) {
if (f(a)) res.push(a);
}
return res;
};
const reduce = (f, acc, iter) => {
if (!iter) {
iter = acc[Symbol.iterator]();
acc = iter.next().value;
}
for (const a of iter) {
acc = f(acc, a);
}
return acc;
};
이런 function들을 연결시켜주는 함수를 만들어 읽기 좋은 코드를 생산해보자
코드를 값으로 다루어보기
go(0, a=>a+1, a=>a+2, a=>a+3, (a) => console.log(a))
위의 첫번째로 받은인자로 부터 0+1+2+3 = 6 이 출력되도록 하는 go함수를 구현하자
const go = (...args) => {
reduce((a, f) => f(a), args); // reduce의 첫번째 인자로 함수, 두번째 인자로 함수들이 들어온다.
} // reduce 내에서 3번째 인자가 안들어옴으로써 args에있는 첫번째 요소를 들고온다.
// 그리고 reduce의 보조함수로써 받은 (첫번째 요소, 함수) => 함수(첫번째 요소)
// 를 하면 args에 있는 나머지 함수들을 iter = Array<function>
// for(const a of iter) (누적결과, a) => a(누적결과)
// 를 함으로써 연속적으로 함수가 실행 결국 6이 출력될 것이다.
함수들이 나열되어있는 합성된 함수를 만드는 pipe를 구현해 보자
const f = pipe(a=>a+1, a=>a+10, a=>a+100) // 함수들을 합성해서 f 함수에 넘겨주는 pipe
const pipe = (...fs) => (a) => go(a, ...fs);
console.log(f(0)) // 111
여기서 f 함수에 인자로 두개가 들어올 경우엔?
const pipe = (f, ...fs) => (...as) => go(f(...as), ...fs)
// f => 첫번째 함수는 인자가 두개가 들어올껄 예상 따로 빼준다.
// ...as => 두개 이상의 인자들은 펼쳐진다.
// f(...as)를 함으로써 실행한 값을 넘겨준다 3이 넘어감
// 나머지 처리들은 위에서 설명했던 go의 동작과 같이 진행됨
// 113 출력
const f = pipe((a,b)=>a+b, a=>a+10, a=>a+100)
conosole.log(f(1,2));
위에서 만든 go를 이용해 좋은 코드를 만들어보자
// need: price 가 15000 이상인 상품들의 가격합
go(products,
products => filter(p=>p.price >= 15000, products),
products => map(p => p.price, products),
prices => reduce(add, prices)
console.log(totalPrice)
)
여기서 좀더 추상적인 개념이 들어가서 더 좋은 코드를 만들어볼수 있는데,
함수를 내가 원하는 시점에 평가하는 관점을 가진 함수를 반환하는 함수를 만들어보자.
const curry = f =>
(a, ..._) => _.length ? f(a, ...) : (..._) => f(a, ..._);
// _.length가 있다는 말은 인자가 2개이상 들어온 경우
// 인자가 2개이상 들어온 경우에는 즉시실행
// 2개 미만인 경우에는 나중에 받은 인자와 함께 실행
const add = curry((a,b) => a + b)
console.log(add(2)(3)) // 5
console.log(add(3)(10)) // 13
// 즉시 실행하지 않고 이후 인자를 기다리도록 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) {
iter = acc[Symbol.iterator]();
acc = iter.next().value;
}
for (const a of iter) {
acc = f(acc, a);
}
return acc;
});
// go로 만든 좋은코드를 더 좋은코드로
go(
products,
filter(p => p.price < 20000), // (products) => filter(p => p.price < 20000)(products)
map(p => p.price), // (products) => map(p => p.price)(products)
reduce(add), // (prices) => reduce(add)(prices)
log);
'Javascript' 카테고리의 다른 글
Naver WEB API 이용해보기 (0) | 2021.11.06 |
---|---|
Browser Back & history API (브라우저 뒤로가기에 대한 대응) (0) | 2021.09.09 |
Javascript ES6+ 에서 함수형 프로그래밍(제너레이터) (0) | 2021.08.21 |
Javascript ES6+ 에서 함수형 프로그래밍(리스트 순회, Iterable Iterator 프로토콜 ) (0) | 2021.08.16 |
Javascript ES6+ 에서 함수형 프로그래밍(평가, 일급, 일차함수, 고차함수) (0) | 2021.08.16 |
Comments