2-5/6. 컬렉션 중심 프로그래밍 - reduce로 map, filter 구현
— Functional programming — 1 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. 명령형으로 짠 filter2function 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. 함수형으로 짠 filter22function 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이 객체이면, 객체형태로 값이 리턴될 수 있도록 만들어보자
isPlainObject()
: plain object인지 확인- entriesIter를 활용하여 객체는 객체 형태로 리턴하도록 개선
entriesIter(coll) -> [k, v]
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 테스트 케이스