똑같은 삽질은 2번 하지 말자

Javascript ES6+ 에서 함수형 프로그래밍(지연성, 지연평가) 본문

카테고리 없음

Javascript ES6+ 에서 함수형 프로그래밍(지연성, 지연평가)

곽빵 2021. 9. 4. 20:43

지연성

어떤 배열을 i번째 까지 구성하고 그 배열의 합을 도출해내는 코드가 있다.

const range = l => {
  let i = -1;
  let res = [];
  while (++i < l) {
    res.push(i);
  }
  return res;
};

var list = range(4); -> [0,1,2,3]
log(list);
log(reduce((a,b)=>a+b, list)); // 6

 

이 코드의 목적이 6이라는 합을 도출하는게 목적이라면?

generator 를 이용해 좀 다른방식으로 짜 볼 수 있다.

 

const L = {};
L.range = function* (l) {
  let i = -1;
  while (++i < l) {
    yield i;
  }
};

var list = L.range(4); // 여기서 반환된 iterator는 아직 next가 안불렸으므로 실행이 안되어있다.
log(list); // L.range {<suspended>} 배열이 만들어져있지 않을걸 확인가능
log(reduce(add, list)); // 6

 

두 코드의 차이는 목적이 6이라는 합을 도출해 내는거라면 보다 효율적인 코드는 밑의 코드가 된다.

iterator는 next()가 호출 되기전에는 실행이 안됨으로, 6이라는 합을 도출해내는데 까지 밑의 코드가 2배정도 빨랐다.

 

지연성의 효율을 좀더 살린 예를 들어보자

특정 범위까지만 뽑아내는 기능을 하는 함수 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]
console.log(take(5, L.range(100))) // [0,1,2,3,4]

 

근데 만약 범위가 100000 이면?

 console.log(take(5, range(100000))) // [0,1,2,3,4]
 console.log(take(5, L.range(100000))) // [0,1,2,3,4]

위의 range는 100000개의 element를 가진 배열을 만들고 take가 실행됨으로써 시간적 차이가 어마어마하게 난다.

 

지연평가 

  • 의도한 타이밍에 계산
  • 실제로 결과가 필요한 시점에서 결과를 산출
  • 의미가 있는 데이터만을 만들어내는 평가\

이전의 javascript에서는 구현이 힘들었지만, ES 6+의 iterable/iterator Protocol을 통해 쉽게 구현이 가능해졌다.

 

 

지연평가에 대한 활용을 좀더 해보자

이전 코드를 값으로 다루기 위해 만들었던 map, filter를 지연평가를하는 함수로 만들어보자면

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;
  }
});

이것들과 L.range를 조합, 그리고 즉시평가함수(take)를 마지막에 넣어 내가 원하는 결과만을 위한 연산만 하는 조합을 만들어보자

 

go(L.range(10),
  L.map(n => n+1),
  L.filter(n => n % 2),
  take(2),
  result => console.log(result))

이것들의 디버그해서 함수의 실행과정을 보면 상당히 흥미로운데

1. 우선 제일먼저 take에 들어가고 for of이 실행되면서 iter의 next()가 호출된다.

2. 호출된 iter는 filter에서 받은 결과이므로 filter 함수에 들어가 filter안의 for of에서 iter의 next가 호출된다.

3. 호출된 iter는 map에서 받은 결과이므로 map 함수에 들어가 map안의 for of에서 iter의 next가 호출된다.

4. map에서 호출된 iter는 L.range의 for of 안으로 들어가 0가 나온다.

5. 그 0는 다시 map으로 들어가 1이 되고 filter에 들어가 조건에 부합하는 홀수이므로 take(2)의 결과에 축적...

6. 이런식으로 반복 실행

 

흔히들 map filter를 쓸때처럼 횡으로 함수가 실행되는게 아니라 종으로 실행되는것이다..

 

 

Comments