#1 reduce함수는 만능이 아니다
"reduce" 한개를 쓰는 것 보다 "map + filter + reduce" 를 함께 쓰자
기존 명령형 습관들 때문에, reduce를 오용하는 경우가 자주 있다.
const users = [
{ name: 'AA', age: 35},
{ name: 'BB', age: 26},
{ name: 'CC', age: 28},
{ name: 'DD', age: 34},
{ name: 'EE', age: 23},
]
위와 같은 users 데이터가 있다. 이를 합산하는 코드를 reduce통해 짜볼 것이다.
console.log(_.reduce((total, u) => total + u.age, 0, users)); // 146
위를 보면 total과 u.age라는 서로 다른 형태를 통해 합산을 하는 것을 알 수 있는데, reduce에서 서로 합산 할 때는 서로 형이 같아야지 더 간단하며 좋은 코드라고 할 수 있다.
다시 말해서, 위 코드 처럼 하나의 보조함수에서 복잡하게 처리하는 것 보다, reduce에 집어넣기 전에 데이터를 통일 시켜서 연산하는 것이 더 좋은 코드라는 말이다.
console.log(_.reduce((a, b) => a + b, L.map(u => u.age, users))); // 146
위와같이 형이 같은 인자가 두개 들어오는 함수의 경우에는 다음처럼 심플하게 코드를 짤 수 있다.
const add = (a, b) => a + b;
const ages = L.map(u => u.age);
console.log(_.reduce(add, ages(users))); // 146
비슷한 예제로 하나 더 보자.
만약, 나이가 30살 미만인 유저의 나이만을 더한다고 했을 때, reduce로만 코드를 짠다면 다음과 같이 나올 것이다.
/* if문 이용 */
console.log(
_.reduce((total, u) => {
if(u.age >= 30) return total;
return total + u.age;
},
0,
users)); // 77
-----------------------------------------------------
/* 삼항 연산자 이용 */
console.log(_.reduce((total, u) => u.age >= 30 ? total : total + u.age, 0, users)); // 77
위와 같이 표현이 가능하지만, map과 filter를 같이 쓰면서, 복잡성을 줄일 수 있다.
console.log(
_.reduce(add,
_.filter(u => u < 30,
_.map(u => u.age, users))));
------------------------------------------
/* 지연적으로 동작하게 하기 */
console.log(
_.reduce(add,
L.filter(u => u < 30,
L.map(u => u.age, users))));
그래서 코드를 만들어 갈때, reduce하나를 사용해서 복잡하게 사용하는 것보다, reduce와 map이나 filter를 같이 사용해서 하나의 형태를 사용하는 인자로 만들어서 사용하는 것이 훨씬 유리하고 이터러블 프로그래밍을 더 잘하게 되는 방법이 될 것 이다.
query1, query2, query3, query4 코드 비교하기
아래 obj1의 출력을 'a=1&c=CC&d=DD'가 대도록 만들어 볼 것이다.
먼저, 각 query 함수들은 다음의 목적을 가지고 구성을 할 것 이다.
- query1: 명령형으로만 구성
- query2: reduce함수만 구성
- query3: "reduce + 다른 함수" 를 통해 구성
- query4: go, pipe, curry 함수를 통해 간단히 구성
const obj1 = {
a: 1,
b: undefined,
c: 'CC',
d: 'DD'
};
/* 실행결과가 'a=1&c=CC&d=DD'가 대도록 만들기 */
query1은 명령형으로 코드를 짜볼 것이다.
function query1(obj){
let res = '';
for(const k in obj){
const v = obj[k];
if(v === undefined) continue;
if(res != '') res += '&';
res += k + '=' + v;
}
return res;
}
console.log(query1(obj1)); //a=1&c=CC&d=DD
쭉 조건을 일치하게 만들어서 나온 코드는 생각보다 복잡한 것을 볼 수 있다.
이를 reduce 하나만을 이용한 함수형으로 짠 query2를 보자.
function query2(obj){
return Object
.entries(obj) // key, value 쌍으로 순회할 수 있게끔 뽑힘
.reduce((query, [k, v], i) => {
if(v === undefined) return query;
return `${query}${i > 0 ? '&' : ''}${k}=${v}`;
}, '');
}
console.log(query2(obj1)); // a=1&c=CC&d=DD
여전히 복잡해 보인다. 그래서 앞에서 배웠던 reduce에 각종 함수를 혼합해서 사용해보자.
const join = (sep, iter) =>
_.reduce((a, b) => `${a}${sep}${b}`, iter);
const query3 = obj =>
join('&',
_.map(([k, v]) => `${k}=${v}`,
_.reject(([_, v]) => v === undefined, //받긴하나 안쓰는 변수는 '_' 로 나타내기
Object.entries(obj))));
console.log(query3(obj1)); // a=1&c=CC&d=DD
마지막으로 go 함수를 통해 함수를 훨씬 읽기 간결하게 정리하는 함수 query4를 보자.
const join = _.curry((sep, iter) =>
_.reduce((a, b) => `${a}${sep}${b}`, iter));
const query4 = obj => _.go(
obj,
Object.entries,
_.reject(([_, v]) => v === undefined),
_.map(join('=')),
join('&')
);
console.log(query4(obj1)); // a=1&c=CC&d=DD
join에 curry를 넣고 go를 이용해서 읽기 간결하게 만들었다. 이 코드는 obj인자를 obj를 그대로 받기때문에 pipe로도 대체 가능하다.
const query4 = _.pipe(
Object.entries,
_.reject(([_, v]) => v === undefined),
_.map(join('=')),
join('&'));
console.log(query4(obj1)); // a=1&c=CC&d=DD
query1에서 query4로 갈수록 더 함수형 프로그래밍적으로 이터러블 가능한 프로그래밍이 가능한데, 훨씬 코드도 간결해지고 읽기도 편해진다.
반대로, 위에서 했었던 query 스트링을 Object로 만드는 코드를 작성해볼 것이다.
▼가장 먼저 split라는 함수로 '&' 기준으로 스트링을 분리해준다.
const split = _.curry((sep, str) => str.split(sep));
const queryToObject = _.pipe(
split('&')
);
console.log(queryToObject('a=1&c=CC&d=DD'));
// ["a=1", "c=CC", "d=DD"]
▼이어서, map을 통해 더 세밀하게 분리해준다.
const split = _.curry((sep, str) => str.split(sep));
const queryToObject = _.pipe(
split('&'),
_.map(split('='))
);
console.log(queryToObject('a=1&c=CC&d=DD'));
// ▼(3) [Array(2), Array(2), Array(2)]
// ▶0: (2) ["a", "1"]
// ▶1: (2) ["c", "CC"]
// ▶2: (2) ["d", "DD"]
// length: 3
// ▶__proto__: Array(0)
▼이후 또 map을 통해 key와 value 쌍으로 합쳐준다.
const split = _.curry((sep, str) => str.split(sep));
const queryToObject = _.pipe(
split('&'),
_.map(split('=')),
_.map(([k, v]) => ({ [k]: v}))
);
console.log(queryToObject('a=1&c=CC&d=DD'));
// ▼(3) [{…}, {…}, {…}]
// ▶0: {a: "1"}
// ▶1: {c: "CC"}
// ▶2: {d: "DD"}
// length: 3
// ▶__proto__: Array(0)
▼마지막으로 reduce를 통해 assign 해주면 다음과 같이 합쳐진다.
const split = _.curry((sep, str) => str.split(sep));
const queryToObject = _.pipe(
split('&'),
_.map(split('=')),
_.map(([k, v]) => ({ [k]: v})),
_.reduce(Object.assign)
);
console.log(queryToObject('a=1&c=CC&d=DD'));
// {a: "1", c: "CC", d: "DD"}
'Web[웹] > ES6+ 함수형 언어' 카테고리의 다른 글
[ES6+: 응용] 객체를 이터러블 프로그래밍으로 다루기 (0) | 2019.10.29 |
---|---|
[ES6+: 응용] map과 filter로 하는 안전한 함수 합성 (0) | 2019.10.29 |
[ES6+: 응용] 명령형에서 함수형으로 변환시키기 (0) | 2019.10.24 |
[JS: ES6+] 비동기: 동시성 프로그래밍 (2) (0) | 2019.10.21 |
[JS: ES6+] 비동기: 동시성 프로그래밍 (1) (0) | 2019.10.17 |