Array.prototype.map보다 개선된 Map
함수형 프로그래밍을 위해선 이터러블 프로토콜을 따르는 객체들을 다루고, 이를 수용할 수 있게 만드는 것이 중요하다.
대표적으로, 유사 배열은 배열이 아니기 때문에 Array.prototype.map 메서드를 사용할 수 없었다.
별도의 배열에 담아두는 작업을 해왔지만, 함수형 프로그래밍을 공부하니, 별도의 map 함수를 만들어두는 것이 좀 더 많은 경우 활용할 수 있어서 좋다.
iterable에 속하는 것으로, String, Array, 유사 배열, TypedArray, Map, Set, 그리고 유저가 직접 정의한 iterable가 속한다.
https://darrengwon.tistory.com/1407
유사 배열은 iterable이기 때문에, 아래와 같은 map 함수를 별도로 만들어 iteration할 수 있다.
const allDom = document.querySelectorAll('*'); // NodeList는 유사 배열
// 기존 Array.prototype.map보다 더 넓은 범위의 map 동작 처리 가능
const map = (f, iter) => {
let names = [];
for (const i of iter) {
names.push(f(i));
}
return names;
};
// allDom의 nodeName만 담은 배열 반환
console.log(map(i => i.nodeName, allDom));
generator를 통해 반환된 iterator도 map으로 순환 가능하구요. iterable인 map, set 다 사용 가능합니다.
function* gen() {
yield 1;
yield 2;
yield 3;
}
const iter = gen();
console.log(map(i => i + 2, iter));
const mapInstance = new Map([
['a', 1],
['b', 2],
]);
console.log(map(([key, _]) => key, mapInstance));
Array.prototype.filter보다 개선된 filter
export const filter = (f, iter) => {
let arr = [];
for (const item of iter) {
if (f(item)) arr.push(item);
}
return arr;
};
console.log(filter(i => i.price <= 20000, products));
Array.prototype.reduce보다 개선된 reduce
export const reduce = (reducer, startAcc, target) => {
let acc = startAcc;
for (const cur of target) {
acc = reducer(acc, cur);
}
return acc;
};
console.log(reduce((acc, cur) => acc + cur, 0, [1, 2, 3, 4, 5]));
Array.prototype.reduce는 초반 acc을 생략하면 Array의 첫 원소를 첫 acc로 설정하는데 이를 아래처럼 작성할 수 있긴하나, Array의 내부 원소가 객체 형태일 경우에는 잘못된 값을 반환한다.
물론 정의 부분에서 들어온 cur의 타입을 체킹한 다음, 객체의 특정 키만 가져오는 방법이 있긴 하겠으나, 그러면 간결함이 부족해지고, 인자가 하나 더 늘어나야하는데 이럴 바엔 그냥 초기 acc 값을 설정해주는 것이 더 효율적이다.
const products = [
{ name: '핫도그', price: 15000 },
{ name: '파인애플', price: 20000 },
];
const a = products.reduce((acc, cur) => acc + cur.price, 0);
console.log(a); // 35000
const b = products.reduce((acc, cur) => acc + cur.price);
console.log(b); // [object Object]20000150003000025000
이런 기존 reduce를 흉내내어서 아래처럼 작성할 수 있긴 합니다만, 그냥 첫 acc값을 제대로 할당하는 버릇을 들이는 게 더 좋아보입니다
export const reduce = (reducer, startAcc, target) => {
// startAcc가 주어지지 않았을 때 target의 첫 인자를 startAcc로 설정
if (!target) {
target = startAcc[Symbol.iterator]();
startAcc = target.next().value;
}
let acc = startAcc;
for (const cur of target) {
acc = reducer(acc, cur);
}
return acc;
};
Conclusion
그래서 결론적으론 다음과 같이 chaining 형식으로 사용할 수 있다.
filter(p => p.price > 20000, products)
.map(p => p.price, products)
.reduce((acc, cur) => acc + cur, 0, products)
'Programming paradigm > 🧱 functional JS' 카테고리의 다른 글
Lazy Evaluation(지연 평가)와 지연성 map, filter (0) | 2021.05.24 |
---|---|
함수형 프로그래밍 개요 : 고차 함수, 클로저, 함수 조합 (0) | 2020.08.25 |
FP을 위한 선언형 프로그래밍(declarative) (0) | 2020.08.25 |
JS 함수형 프로그래밍을 위한 사전 지식 : 순수함수, 일급함수, 커링 (0) | 2020.07.05 |