Skip to content
Underbleu
GithubLinkedin

3-7. reduce에 대한 질문과 리팩토링

Functional programming1 min read

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

# reduce() 리팩토링. 재귀 제거

reduce 함수의 acc가 프로미스인 경우 .then체인 걸어주는 방식으로 바꾸면, 재귀를 돌지 않아도 된다

1/*
2function reduce(f, coll, acc) {
3 const iter = collIter(coll)
4 acc = acc === undefined ? iter.next().value : acc
5 return then2(acc, function recur(acc) {
6 for(const a of iter) {
7 acc = f(acc, a)
8 if(acc instanceof Promise) return acc.then(recur)
9 }
10 return acc
11 })
12} */
13
14function reduce(f, coll, acc) {
15 const iter = collIter(coll);
16 acc = acc === undefined ? iter.next().value : acc;
17 for (const a of iter) {
18 acc = acc instanceof Promise ? acc.then((acc) => f(acc, a)) : f(acc, a);
19 }
20 return acc;
21}
22
23log(reduce((a, b) => a + b, { a: 1, b: 2, c: 3 })); // 6
24log(reduce((a, b) => Promise.resolve(a + b), { a: 1, b: 2, c: 3 }, 10)); // 16
25log(reduce((a, b) => a + b, { a: 1, b: 2, c: 3 }, Promise.resolve(100))); // 106
26log(reduce((a, b) => Promise.resolve(a + b), { a: 1, b: 2, c: 3 }, Promise.resolve(1000))); // 1006

# gen() 함수 제거

원래 제너레이터로 만든 이터러블이 재귀를 돌기위해 for...of를 빠져나가면, 이터러블이 가지고 있던 return메소드를 실행시켜 이터러블을 종료시켜버리는 문제가 있었다. 그래서 gen함수로 제너레이터 함수들을 감싸줬었다 (제너레이터로 생성한 이터러블의 return 메소드를 감춰주며, 이터레이터 동작을 위임)

하지만 이제 reduce에서 재귀를 돌지 않기때문에 콜스택에서 빠져나갈일이 없다. 즉, for...of를 빠져나가 이터러블의 return메서드가 실행되어 이터러블이 중간에 중단될일 이 없다. return메서드를 감춰주기 위해 사용하던 gen함수를 제거하자

1/*
2var valuesIter = gen(function*(obj) {
3 for (const k in obj) yield obj[k];
4})
5
6var entriesIter = gen(function*(obj) {
7 for (const k in obj) yield [k, obj[k]];
8}) */
9
10function* valuesIter(obj) {
11 for (const k in obj) yield obj[k];
12}
13
14function* entriesIter(obj) {
15 for (const k in obj) yield [k, obj[k]];
16}

# findVal(), findValC리팩토링

gen함수를 제거 하고나면

  • coll로 객체가 와서 valuesIter 제너레이터로 iter를 만들고
  • 보조함수로 Promise가 와서 재귀를 돌아야 하는 경우에

재귀를 돌기위해 for...of를 빠져나갈 경우 iter가 중단되는 문제가 생긴다

"for...of를 while로 대체 하자"

findVal()

기존의 findVal에서는 재귀를 도는순간 이터러블이 종료되었기 때문에, 찾는 값이 이터러블의 첫번째 값이 아닌 이상 항상 undefined를 반환하는 문제가 있었다

1/*
2const findVal = curry((f, coll) => {
3 const iter = collIter(coll)
4 return function recur(res) {
5 for (const a of iter) {
6 if((res = f(a)) !== undefined) {
7 return go(res, res => res !== undefined ? res : recur())
8 }
9 }
10 } ()
11}) */
12
13var findVal = curry((f, coll) => {
14 const iter = collIter(coll);
15 return (function recur(cur, res) {
16 while ((cur = iter.next()) && !cur.done) {
17 if ((res = f(cur.value)) !== undefined) {
18 return go(res, (res) => (res !== undefined ? res : recur()));
19 }
20 }
21 })();
22});
23
24go(
25 [1, 2, 3, 4, 5],
26 findVal((a) => Promise.resolve(a > 3 ? a : undefined)),
27 log
28); // 4
29go(
30 { a: 1, b: 2, c: 3, d: 4, e: 5 },
31 findVal((a) => Promise.resolve(a > 3 ? a : undefined)),
32 log
33); // 4

findValC()

findValC의 iter는 stepIter에게 동작을 위임하기 때문에 coll로 객체가 오고, 보조함수가 프로미스여도 문제는 없다.

하지만 코드의 일관성을 위해 리팩토링 하자

1/*
2var findValC = curry((f, coll, limit = Infinity) => {
3 const iter = stepIter(collIter(coll), limit)
4 return new Promise(function recur (resolve) {
5 let i = 0, j = 0
6 for (const a of iter) {
7 ++i
8 go(a, f,
9 b => b === undefined ? undefined : resolve(b),
10 _ => (i == ++j) ? iter.remain ? recur(resolve) : resolve() : undefined)
11 }
12 })
13}) */
14
15var findValC = curry((f, coll, limit = Infinity) => {
16 const iter = stepIter(collIter(coll), limit);
17 return new Promise(function recur(resolve) {
18 let i = 0,
19 j = 0,
20 cur;
21 while ((cur = iter.next()) && !cur.done) {
22 ++i;
23 go(
24 cur.value,
25 f,
26 (b) => (b === undefined ? undefined : resolve(b)),
27 (_) => (i == ++j ? (iter.remain ? recur(resolve) : resolve()) : undefined)
28 );
29 }
30 });
31});
32
33go(
34 { a: 1, b: 2, c: 3, d: 4, e: 5 },
35 findVal((a) => time(a > 3 ? a : undefined)),
36 log
37); // 4 --> 4초소요
38
39go({ a: 1, b: 2, c: 3, d: 4, e: 5 }, (nums) => findValC((a) => time(a > 3 ? a : undefined), nums, 5), log); // 4 --> 1초소요