이터러블을 까먹었다면? → 이터러블 연구소

이터러블/ 이터레이터를 이용하여 map, filter, reduce 구현해보기


const map = (f, iter) => {
        if (!iter[Symbol.iterator]) {
          return;
        }

        const res = [];

        for (const value of iter) { 
				// iterator를 순회하며 콜백함수를 실행후 결과에 저장
          res.push(f(value));
        }

        return res;
      };

const filter = (f, iter) => { 
        const res = [];

        for (const value of iter) {
          if (f(value)) {
            res.push(value);
          }
        }

        return res;
      };

const reduce = (f, iter, acc) => {
        if (!acc && typeof acc != "number") { // acc 비교부분 수정필요 인덱스값도 사용하고 싶어
          iter = iter[Symbol.iterator](); 
          acc = iter.next().value; // acc가 없을 경우 acc를 만들어 iterable를 한번 next함 
        }

        for (const value of iter) {
          acc = f(acc, value); // 이터러블 순회하며 acc에 누적
        }
        return acc;
      };

이터러블을 받아 순회한다.

reduce에서는 누적값이 없을경우 이터레이터를 한번 순회하여 초기값을 이터러블의 첫번째 인자로 사용하는 방식으로 구현함

Reduce를 이용하여 go, pipe, curry 구현해보기


const go = (...args) => reduce((acc, currFunc) => currFunc(acc), args);
// 인자들을 받는다. 인자들을 순회하며 reduce로 누적하는데, 
// 초기값이후 인자로 받은 함수들을 순차적으로 실행하며 누적한다.

	ex) go(
          0,
          (a) => a + 1,
          (a) => a + 10,
          (a) => a + 100
        ) // 111

const pipe = (...funcs) => (value) => go(value, ...funcs/*스프레드 문법없을시 funcs는 배열이므로 에러*/);
// 파이프 함수 특징은 클로저라는 점이다. 
// 인자로 함수들을 받고 이후 함수를 리턴한다. 
// 리턴한 함수로부터 value를 받아 go 함수를 통해 순차적으로 함수들을 적용한다.
// 함수들을 합성하는데 용이하다.

------- 문제점 ------

// 무엇이 문제일까?
const problem = pipe((a,b)=> a + b, (a)=> a + 1) 

problem(1, 2); // 결과는?

//문제점은 현재 파이프함수가 인자가 하나일 경우만을 고려하여 짜여졌다는 점이다.
// 1. problem 자체는 문제가 없지만 인자 1, 2중 1만이 넘어가게 되어 결과는 NaN이된다. 1 + undefined
// 해결책으로 (a, b)=>a + b 를 add란 이름으로 빼내어 problem(add(1,2)) 로도 가능하지만 가독성이 떨어진다.

// 해결책

const pipe = (f,...funcs) => (...values) => go(f(...values),...funcs))
// pipe함수가 리턴한 클로저 함수를 실행할때 go의 첫번째 인자는 처음받은 함수들중 첫번째 함수가 적용된다. 
// 이후 클로저의 인자들을 모두 적용하여 계산을 진행한다. 

const curry = (f) => (arg, ...rest) =>
	rest.length ? f(arg, ...rest)
		: (...oneMoreArgs) => f(arg, ...onemoreArgs)
// 커리 함수는 인자에 따라 클로저함수의 리턴값이 달라진다.
// 커리 함수는 함수를 받아 저장하고 클로저 함수를 리턴한다.
// 클로저 함수는 인자가 2개 이상 경우 저장한 함수를 즉시 실행한다.
// 클로저 함수의 인자가 1개일 경우 한번더 함수를 리턴하여 인자를 받아 함수를 실행하게 된다.

const 곱하기 = curry((a, b)=> a * b);
곱하기(1, 2); // 인자가 2개 이상이므로 저장한 첫번째 콜백함수가 실행되어 2가 나옴
곱하기(1)(2); // 클로저인 곱하기의 인자가 1개였으므로 함수를 리턴함, 
							// 리턴한 함수에 다시 인자를 받아 저장했던 콜백한수를 실행 

Curry를 이용하여 코드의 품질 향상