3-1. map, filter 계열 함수 만들기
— Functional programming — 1 min read
프로그래머스에서 진행한 유인동님의 ES6로 알아보는 동시성 & 함수형 프로그래밍 강의를 들으며 정리한 내용입니다.
# Javascript Basic
Map과 Set
ES6에 새로 도입된 데이터구조
Map - 객체의 단점을 해결한 데이터 구조
- size 프로퍼티 - key-value 가 몇 개 인지 확인 가능
- 객체의 key로 다양한 타입사용 가능 (숫자, 객체...)
- 프로퍼티의 순서를 보장
Set - 배열과 비슷하지만, 중복을 허용않는 데이터 구조
1const m = new Map()2m.set(1, 'a')3m.set({}, 'b')4m.size // 25log(m) // Map(2) {1 => "a", {…} => "b"}6
7const s = new Set()8s.add('a')9s.add('b')10s.add('a') // --> 중복데이터. 무시11s.size // 212log(s) // {"a", "b"}
Iterable computed data
ES6의 data-structure (Arrays, Typed Arrays, Maps, Sets)는 well-formed 이터러블을 리턴해주는 3가지 메서드를 가지고 있다
keys()
- key를 담고있는 Iterator object 리턴values()
- value를 담고있는 Iterator object 리턴entries()
- [key, value]를 담고있는 Iterator object 리턴- 배열의 경우 [index, element]
- Set의 경우 [key, key]
1[][Symbol.iterator]() // Array Iterator {}2[].values() // Array Iterator {}3
4const arr = ['a', 'b', 'c']5const arrEntries = arr.entries()6
7arrEntries === arrEntries[Symbol.iterator]() 8// true -> well-formed 이터러블9
10arrEntries.next().value11// [0, 'a'] -> 1. Remember12
13for (const pair of arr.entries()) log(pair)14// [1, 'b'] -> 2. Continue with same Iterator15// [2, 'c']
# baseMF 리팩토링
리팩토링 전
1const baseMF = (f1, f2) => (f, coll) =>2 hasIter(coll)3 ? reduce(f1(f), coll, [])4 : reduce(f2(f), entriesIter(coll), {});5
6const map = baseMF(7 f => (res, a) => then2(f(a), b => push(res, b)),8 f => (res, [k, a]) => then2(f(a), b => set(res, k, b))9)10
11const filter = baseMF(12 f => (res, a) => then2(f(a), b => b ? push(res, a) : res),13 f => (res, [k, a]) => then2(f(a), b => b ? set(res, k, a) : res),14)
리팩토링 후
1const baseMF = (f1, f2) => (f, coll) =>2 hasIter(coll) ?3 reduce((res, a) => then2(f(a), b => f1(res, a, b)), coll, []) :4 reduce((res, [k, a]) => then2(f(a), b => f2(res, k, a, b)), entriesIter(coll), {})5
6const map = baseMF(7 (res, a, b) => push(res, b), 8 (res, k, a, b) => set(res, k, b))9
10const filter = baseMF(11 (res, a, b) => b ? push(res, a) : res,12 (res, k, a, b) => b ? set(res, k, a) : res)
# map계열 함수
values()
- 객체의 value를 추출하는 함수
Object.values()
메서드에선 지원하지 못하는 Set, Map등의 값도 받을 수 있도록 다형성을 높혀보자
Object.values()
(ES8)- 객체의 속성중, 부수속성이 "
enumerable: true
"한 값을 배열로 리턴 - 내부적으로 for...in처럼 key값을 이용해 순회하기 때문에 Set, Map에 사용불가
- 객체의 속성중, 부수속성이 "
_1. map을 활용한 values()
함수 만들기
- 기존의
map()
함수를 이용하면 Set은 잘 동작하지만, Map, Plain Object는 대응안됨 - Map은 디폴트
[Symbol.iterator]
로 entries를 사용하기 때문에 값만 추출되지 않고 key-value쌍으로 출력된다 - Plain Object 역시 key-value쌍으로 출력된다
1const values1 = coll => map(a => a, coll)2
3values1(new Set([1, 2, 3, 4])) // [1, 2, 3, 4]4values1(new Map([['a', 1], ['b', 2]])) // [["a", 1], ["b", 2]]5values1({a:1, b:2})) // {a: 1, b: 2}
1Map.prototype[Symbol.iterator] // ƒ entries() { ... }2Set.prototype[Symbol.iterator] // ƒ values() { ... }
_2. Map 대응
Map이 들어오면 [Symbol.iterator]
로 values를 사용하도록 설정
1const values2 = coll =>2 coll instanceof Map ?3 map(a => a, coll.values()) :4 map(a => a, coll);5
6values2(new Map([['a', 1], ['b', 2]])) // [1, 2]
_3. Plain Object 대응
Map이외의 값은 collIter를 통과시켜 valuesIter로 값만 추출될 수 있도록 한다
1const collIter = coll =>2 hasIter(coll) ? coll[Symbol.iterator]() : valuesIter(coll)3 4const values3 = coll =>5 coll instanceof Map ?6 map(a => a, coll.values()) :7 map(a => a, collIter(coll));8
9values3({ a: 1, b: 2 }) // [1, 2]
_4. 완성
values()
는 단순히 값을 추출하는 역할을 넘어, 값을 평가하는 확장성까지 있는 함수이다
1const values = coll =>2 map(identity, coll instanceof Map ? coll.values() : collIter(coll))3
4values(function *() {5 yield 1;6 yield 2;7 yield 3;8} ())9// [1, 2, 3] -> 값을 평가하는 확장성
entries()
- 객체의 [key, value]를 추출하는 함수
1var entries = coll =>2 map(identity, hasIter(coll) ? coll.entries() : entriesIter(coll))3
4entries(new Set(['a', 'b'])) 5// [['a', 'a'], ['b', 'b']]6
7entries(new Map([['a', 1], ['b', 2]])) 8// [['a', 1], ['b', 2]]
# filter계열 함수
reject()
- falsy한 값을 추출하는 함수
- reject는 falsy한 값을
- filter는 truthy한 값을 추출
_1. filter를 활용하여 falsy한 값을 뽑는 함수 만들어보기
1const reject = (f, coll) => filter(a => !f(a), coll);2
3console.log(filter(a => a % 2, [1, 2, 3, 4])); // [1, 3]4console.log(reject(a => a % 2, [1, 2, 3, 4])); // [2, 4]
_2. 보조함수에서 프로미스를 리턴했을 때
Promise 값을 부정할 수 없는 문제
-> 비동기가 일어날 수 있는 곳이라면 "하나의 값에, 하나의 평가"만 이루어지도록하자
not()
- 보조함수f(a)
의 리턴값을 then함수로 감싸서, Promise이던 아니던 모두 부정할 수 있게해줌- filter가 다형성을 지원하면, reject역시 다형성을 지원하는 범위가 늘어난다
1const not = a => then2(a, a => !a);2const reject = (f, coll) => filter(a => not(f(a)), coll);3
4filter(a => Promise.resolve(a % 2), [1, 2, 3, 4]) // [1, 3]5reject(a => Promise.resolve(a % 2), [1, 2, 3, 4]) // [2, 4]
compact()
- truthy한 값만 남게하는 함수
1const compact = coll => filter(identity, coll);2
3compact([ 0, false, {}, 10, null ]) // [{ }, 10]
# 의존성이 없는 함수형프로그래밍
형을 정확히 다루면서, 계열중심의 함수(map, filter..)들을 사용하여 함수형 프로그래밍을 하면
- 계층구조가 있는 함수라도 서로 절대 의존성이 없다는 보장을 할 수 있다
- 즉, 위에 있는 부모격의 함수가 수정되었을때, 자식 함수가 오작동할 리가 없다는 보장이 된다
- 반면에, 객체지향에선 상위 객체를 고치면 하위의 객체 들은 거의 영향을 받는다