해당 강의를 듣고 정리한 글입니다.... 🐿️
https://www.inflearn.com/course/functional-es6
range 함수를 만들어보자. 파이썬의 range 함수처럼 연속적인 숫자 객체를 만들어서 반환해주는 함수를 만들려는 것 같다.
console.log(range(5)); // [0, 1, 2, 3, 4]
아래와 같이 range 함수를 만들 수 있다.
const range = (l) => {
let i = -1;
let res = [];
while (++i < l) {
res.push(i);
}
return res;
};
저번 시간에 구현해봤던 reduce 함수를 사용하여 range로 만든 배열의 총 합도 구할 수 있다.
const add = (a, b) => a + b;
const list = range(5);
console.log(reduce(add, list)); // 10
강의에서 하라는 대로 느긋한 L.range도 만들어 보자. => 최대한 연산을 미루기 때문에 느긋한 L.range라고 명명하신것 같다-!
const L = {};
L.range = function* (l) {
let i = -1;
while (++i < l) {
yield i;
}
};
const add = (a, b) => a + b;
const list = L.range(5);
console.log(reduce(add, list)); // 10
두 개의 range 함수에서는 차이가 있다. 첫 번째 range 함수에서는 range() 함수를 실행하면 모든 값들이 평가가되어 list 배열이 만들어진다. 하지만 두 번째 range 함수에서는 reduce함수에서 list.next().value가 실행되는 순간 값이 평가되어진다.
두 개의 range 함수의 성능을 비교해보자.
function test(name, time, f) {
console.time(name);
while (time--) f();
console.timeEnd(name);
}
test("range", 10, () => reduce(add, range(1000000)));
test("L.range", 10, () => reduce(add, L.range(1000000)));
결과는 range: 559.301ms, L.range: 389.364ms로 나왔다.
원하는 숫자만큼 배열을 자르는 take 함수를 작성해보자.
const take = (l, iter) => {
let res = [];
for (const a of iter) {
res.push(a);
if (res.length == l) return res;
}
return res;
};
console.log(take(5, range(100))); // [0, 1, 2, 3, 4]
L.range 함수도 take 함수 사용이 가능하다.
L.range 함수의 장점은 range 함수에서는 1000000 만큼의 배열을 만드는데 L.range 함수는 딱 필요한 5개의 숫자만 가지는 배열을 만들기 때문에 효율적이다.
console.log(take(5, range(100000))); // 2.308ms
console.log(take(5, L.range(100000))); // 0.009ms
여기서 의문점, take 에서 원하는 배열의 크기만 가지도록 하기 때문에 효율적인 것은 알았다.
test("range", 10, () => reduce(add, range(1000000))); test("L.range", 10, () => reduce(add, L.range(1000000)));
그러나, 이 테스트 에서는 똑같이 100000개의 숫자로 이루어진 배열을 reduce 하는데 왜 효율적인 것 일까? 이 부분에 대해서는 좀 더 알아봐야겠다..!
기존에 작성했던 map과 차이점은 map을 했다고 해서 배열이 만들어지는게 아니라 next()를 해줄 때 값이 평가된다.
L.map = function* (f, iter) {
for (const a of iter) yield f(a);
};
const it = L.map((a) => a + 10, [1, 2, 3]);
console.log(it.next()); // 11
console.log(it.next()); // 12
console.log(it.next()); // 13
filter도 마찬가지로 next()를 해줄 때 값이 평가된다.
L.filter = function* (f, iter) {
for (const a of iter) if (f(a)) yield a;
};
const it = L.filter((a) => a % 2, [1, 2, 3, 4]);
console.log(it.next()); // 1
console.log(it.next()); // 3
지난 시간과 이번에 공부했던 코드들이다.
const curry =
(f) =>
(a, ..._) =>
_.length ? f(a, ..._) : (..._) => f(a, ..._);
const map = curry((func, iter) => {
let res = [];
for (const a of iter) {
res.push(func(a));
}
return res;
});
const filter = curry((func, iter) => {
let res = [];
for (const a of iter) {
if (func(a)) res.push(a);
}
return res;
});
const reduce = curry((func, acc, iter) => {
if (!iter) {
iter = acc[Symbol.iterator]();
acc = iter.next().value;
}
for (const a of iter) {
acc = func(acc, a);
}
return acc;
});
const add = (a, b) => a + b;
const go = (...args) => {
reduce((a, f) => f(a), args);
};
const range = (l) => {
let i = -1;
let res = [];
while (++i < l) {
res.push(i);
}
return res;
};
const L = {};
L.range = function* (l) {
let i = -1;
while (++i < l) {
yield i;
}
};
const take = curry((l, iter) => {
let res = [];
for (const a of iter) {
res.push(a);
if (res.length == l) return res;
}
return res;
});
L.map = curry(function* (f, iter) {
for (const a of iter) yield f(a);
});
L.filter = curry(function* (f, iter) {
for (const a of iter) if (f(a)) yield a;
});
range, map, filter, take를 사용했을 때와 L.range, L.map, L.filter, take를 사용했을 때의 평가 순서 비교를 해보려고 한다.
go(
range(10),
map((n) => n + 10),
filter((n) => n % 2),
take(2),
console.log
);
go(
L.range(10),
L.map((n) => n + 10),
L.filter((n) => n % 2),
take(2),
console.log
);
첫 번째 경우에는 모든 배열의 요소가 range[0,1,2,3,4,5,6,7,8,9] > map[10,11,12,13,14,15,16,17,18,19] > filter[11,13,15,17,19] > take[11,13]
이런 가로 방식으로 전개가 되고,
두 번째 경우에는 range[0] > map[10] > filter[], range[1] > map[11]> filter[1] > take[11]
하나씩 세로 방식으로 전개가 된다.
사용하는 데이터가 무엇이든지, 사용하는 보조 함수가 순수함수라면
위의 코드 예시처럼 모든 배열의 요소가 mapping -> filtering 하던지, 배열 한개씩 mapping -> filtering 하던지 결과가 같다.
서로 다른 라이브러리라 던지 서로 다른 함수들이던지 약속된 자바스크립트의 기본 값을 통해 소통하기 때문에 조합성이 높고, 합성을 안전하게 할 수 있다.