Skip to content
Underbleu
GithubLinkedin

2-5/6. 컬렉션 중심 프로그래밍 - reduce로 map, filter 구현

Functional programming1 min read

프로그래머스에서 진행한 유인동님의 ES6로 알아보는 동시성 & 함수형 프로그래밍 강의를 들으며 정리한 내용입니다.

# reduce로 map 구현하기

1const push = (arr, v) => (arr.push(v), arr);
2const thenR = (a, f) => a instanceof Promise ? a.then(f) : f(a)
3const map = (f, coll) =>
4 reduce((res, a) => thenR(f(a), b => push(res, b)), coll, [])
5
6map(a => Promise.resolve(a + 1000), [1, 2, 3]).then(console.log)
7// [1001, 1002, 1003]
  • push(): 작은함수의 독립 배열에 요소를 집어넣는 보조함수
    • return a, b; // b -> 뒤의 값이 리턴되는 원리를 이용
    • 기존의 명령형코드는 해석 해야했지만, 함수이름 덕분에 코드가 직관적으로 읽혀지게 된다
  • thenR(): 가독성이 좋은 비동기제어를 위한 함수
    • 보조함수가 Promise일 경우, 리턴값을 처리하기 위함
    • 인자를 받아서 오른쪽 함수에 넣는다. 왼->오 읽기가 훨씬 편해짐

함수형 프로그래밍을 하지 않으면, 함수를 값으로 리턴으로 받아서 쓰는경우는 거의 없다.

우리는 앞으로 계속 값이 통과되는, 인자와 리턴값으로만 소통하는 패러다임의 프로그래밍을 할 것 이다


# 함수형 프로그래밍. 인자 이름 컨벤션

  • 인자이름을 a -> b -> c 로 써주면 흐름을 따라가면서 읽기가 편해진다
  • 요즘 filter의 보조함수를 predi보단 f로 이름 짓는편이다
  • 인자이름을 의미에 맞게 짓는것 보다, a, b, c같은 규칙적인 인자이름에 익숙해지면 코드를 읽기 편하고 이해하기 쉽다
  • ex) ramda.js문서의 인자 이름 규칙
    하스켈 타입표기법을 js버전으로 축약표현함
    1```
    2`f(a) => b` a를 함수에 통과시켜, b를 만든다
    3```

1. 명령형 filter() vs 함수형 filter()

함수형으로 코드를 짰을때, 얼마나 코드가 간결해졌는지 알 수 있다

1//1. 명령형으로 짠 filter
2function filter(f, coll) {
3 const res = [];
4 const iter = coll[Symbol.iterator]();
5 return function recur() {
6 for(const a of iter) {
7 const b = f(a);
8 if (b instanceof Promise) {
9 return b.then(function(b) {
10 if(b) res.push(a);
11 return recur();
12 })
13 } else {
14 if(b) res.push(a);
15 }
16 }
17 return res;
18 } ();
19}
20
21//2. 함수형으로 짠 filter
22function filter(predi, coll) {
23 return reduce((res, val) =>
24 thenR(predi(val), bool => bool ? push(res, val) :res), coll, [])}
25
26console.log(filter(a => a % 2, [1,2,3,4])); //[1, 3]
27filter(a => Promise.resolve(a % 2), [1,2,3,4]).then(console.log); //[1, 3]

이 정도 수준은 underscore.js 정도 (배열과 객체의 값을 받아 map, filter)
-> 하지만, underscore.js는 프로미스까지 해결은 못한다. 우리는 가능하다 짱이다!!!

2. 객체는 객체로, 배열은 배열로 반환하도록

현재는 coll이 배열이던 객체이던 value만 추출하여 map을 돌린다. coll이 객체이면, 객체형태로 값이 리턴될 수 있도록 만들어보자

  1. isPlainObject(): plain object인지 확인
  2. entriesIter를 활용하여 객체는 객체 형태로 리턴하도록 개선
    entriesIter(coll) -> [k, v]
  3. set(): mapping된 k, v를 res에 세팅
1function *entriesIter(obj) {
2 for(const k in obj) yield [k, obj[k]];
3}
4
5const isPlainObject = obj => obj.constructor == Object;
6const push = (arr, v) => (arr.push(v), arr);
7const set = (obj, k, v) => (obj[k] = v, obj);
8
9const map = (f, coll) =>
10 isPlainObject(coll) ?
11 reduce(
12 (res, [k, a]) => thenR(f(a), b => set(res, k, b)),
13 entriesIter(coll),
14 {}) :
15 reduce(
16 (res, a) => thenR(f(a), b => push(res, b)),
17 coll,
18 [])

도큐먼트보다 코드나 테스트케이스를 읽어보면 공부가 많이 된다
ex) underscore.js 테스트 케이스